构建基于 JPA 的 Hibernate 环境

——跟我一起学 Hibernate 系列(2)

1. 主要的开发环境

  • Maven 3.3.9
  • idea 14.1.1
  • Bitronix 2.1.3(JTA 事务)

2. pom.xml

  • 所有的依赖包由 Maven 统一管理
  • 跟我一起学 Hibernate 系列中所有的特性展示,都基于这次构建的开发环境
<properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <maven.compiler.source>1.7maven.compiler.source>
        <maven.compiler.target>1.7maven.compiler.target>

        
        <hibernate.jpa21.api.version>1.0.0.Finalhibernate.jpa21.api.version>

        
        <hibernate.version>5.1.0.Finalhibernate.version>

        
        <validation.api.version>1.1.0.Finalvalidation.api.version>

        
        <hibernate.validator.version>5.2.1.Finalhibernate.validator.version>
        <javax-el.version>3.0.1-b04javax-el.version>

        
        <slf4j.impl.version>1.6.1slf4j.impl.version>

        
        <testing.version>6.8.7testing.version>

        
        <btm.version>2.1.3btm.version>

    properties>

    
    <dependencies>

        
        <dependency>
            <groupId>org.testnggroupId>
            <artifactId>testngartifactId>
            <version>${testing.version}version>
            <exclusions>
                <exclusion>
                    <groupId>junitgroupId>
                    <artifactId>junitartifactId>
                exclusion>
            exclusions>
        dependency>

        
        <dependency>
            <groupId>org.slf4jgroupId>
            <artifactId>slf4j-jdk14artifactId>
            <version>${slf4j.impl.version}version>
        dependency>

        
        <dependency>
            <groupId>org.codehaus.btmgroupId>
            <artifactId>btmartifactId>
            <version>${btm.version}version>
        dependency>

        
        <dependency>
            <groupId>org.hibernategroupId>
            <artifactId>hibernate-entitymanagerartifactId>
            <version>${hibernate.version}version>
        dependency>

        
        <dependency>
            <groupId>org.hibernategroupId>
            <artifactId>hibernate-validatorartifactId>
            <version>${hibernate.validator.version}version>
        dependency>

        
        <dependency>
            <groupId>javax.elgroupId>
            <artifactId>javax.el-apiartifactId>
            <version>${javax-el.version}version>
        dependency>
        <dependency>
            <groupId>org.glassfishgroupId>
            <artifactId>javax.elartifactId>
            <version>${javax-el.version}version>
        dependency>

        
        <dependency>
            <groupId>org.hibernategroupId>
            <artifactId>hibernate-enversartifactId>
            <version>${hibernate.version}version>
        dependency>

        
        <dependency>
            <groupId>org.hibernategroupId>
            <artifactId>hibernate-ehcacheartifactId>
            <version>${hibernate.version}version>
        dependency>


        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>5.1.34version>
        dependency>


    dependencies>

3. 开发环境基础类

  • 这些类都放在 env 包中

3.1 数据库产品类

package net.deniro.hibernate.env;

import bitronix.tm.resource.jdbc.PoolingDataSource;

import java.util.Properties;

/**
 *
 * 数据库产品(目前只支持 MYSQL)
 *
 * @author Deniro Li
 *         2017/1/13
 */
public enum DatabaseProduct {

    MYSQL(
            new DataSourceConfiguration() {

                @Override
                public void configure(PoolingDataSource ds, String connectionURL) {
                    ds.setClassName("bitronix.tm.resource.jdbc.lrc.LrcXADataSource");
                    ds.getDriverProperties().put(
                            "url",
                            connectionURL != null ? connectionURL
                                    : "jdbc:mysql://localhost:3306/hibernate?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull"
                    );
                    Properties dp=ds.getDriverProperties();
                    dp.put("driverClassName", "com.mysql.jdbc.Driver");
                    dp.put("user","root");
                    dp.put("password","");
                    ds.setDriverProperties(dp);
                }
            },
            //MySQL57InnoDBDialect 可用于 MySQL5.6
            org.hibernate.dialect.MySQL57InnoDBDialect.class.getName()
    );

    public DataSourceConfiguration configuration;
    public String hibernateDialect;

    private DatabaseProduct(DataSourceConfiguration configuration, String hibernateDialect) {
        this.configuration = configuration;
        this.hibernateDialect = hibernateDialect;
    }

    public interface DataSourceConfiguration {
        void configure(PoolingDataSource ds, String connectionURL);
    }
}

3.2 使用 Bitronix 作为数据库事务

package net.deniro.hibernate.env;

import bitronix.tm.TransactionManagerServices;
import bitronix.tm.resource.jdbc.PoolingDataSource;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;
import java.util.logging.Logger;

/**
 * 使用 Bitronix 作为数据库事务
 *
 * @author Deniro Li
 *         2017/1/13
 */
public class TransactionManagerSetup {

    public static final String DATASOURCE_NAME = "deniroDS";

    private static final Logger logger = Logger.getLogger(TransactionManagerSetup.class
            .getName());

    protected final Context context = new InitialContext();
    protected final PoolingDataSource dataSource;
    public final DatabaseProduct databaseProduct;

    public TransactionManagerSetup(DatabaseProduct databaseProduct) throws Exception {
        this(databaseProduct, null);
    }


    public TransactionManagerSetup(DatabaseProduct databaseProduct, String connectionURL)
            throws Exception {
        logger.fine("启动数据库连接池");

        logger.fine("为事务设置一个稳定的唯一标识");
        TransactionManagerServices.getConfiguration().setServerId("deniroServer1");

        logger.fine("关闭 JMX(为了单元测试方便)");
        TransactionManagerServices.getConfiguration().setDisableJmx(true);

        logger.fine("关闭事务日志(为了单元测试方便)");
        TransactionManagerServices.getConfiguration().setJournal(null);

        logger.fine("关闭在一个事务中无法获取数据库连接的警告信息");
        TransactionManagerServices.getConfiguration().setWarnAboutZeroResourceTransaction
                (false);

        logger.fine("创建数据库连接池");
        dataSource = new PoolingDataSource();
        dataSource.setUniqueName(DATASOURCE_NAME);
        dataSource.setMinPoolSize(1);
        dataSource.setMaxPoolSize(5);
        dataSource.setPreparedStatementCacheSize(10);

        // 这里明确指定事务隔离级别,为了后面的高级特性展示
        dataSource.setIsolationLevel("READ_COMMITTED");


        //当 EntityManager 被挂起或者没有被加入事务的情况下,允许事务自动提交
        dataSource.setAllowLocalTransactions(true);

        logger.info("选定的数据库是:" + databaseProduct);
        this.databaseProduct = databaseProduct;
        databaseProduct.configuration.configure(dataSource, connectionURL);

        logger.fine("初始化事务与资源管理器");
        dataSource.init();
    }

    public Context getNamingContext() {
        return context;
    }

    public UserTransaction getUserTransaction() {
        try {
            return (UserTransaction) getNamingContext().lookup("java:comp/UserTransaction");
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    public DataSource getDataSource() {
        try {
            return (DataSource) getNamingContext().lookup(DATASOURCE_NAME);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void rollback() {
        UserTransaction tx = getUserTransaction();
        try {
            if (tx.getStatus() == Status.STATUS_ACTIVE || tx.getStatus() == Status.STATUS_MARKED_ROLLBACK)
                tx.rollback();
        } catch (SystemException e) {
            System.err.print("事务回滚失败!");
            e.printStackTrace(System.err);
        }
    }

    public void stop() throws Exception {
        logger.fine("关闭数据库连接池");
        dataSource.close();
        TransactionManagerServices.getTransactionManager().shutdown();
    }
}

3.3 JNDI 配置

  • 底层的 Bitronix 是使用 JNDI 来创建数据库连接池的
  • 文件路径在 /resources 下
# Bitronix 内建了一个 JNDI contgext,所以这里直接绑定对应的类就好
java.naming.factory.initial=bitronix.tm.jndi.BitronixInitialContextFactory

3.4 单元测试基础类

  • 所有的 Hibernatge 单元测试都继承这个类
package net.deniro.hibernate.env;

import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.Optional;
import org.testng.annotations.Parameters;

import java.util.Locale;

/**
 * 在一个单元测试中,开启或关闭事务管理器或者数据库连接池
 *  
 *
 * @author Deniro Li
 *         2017/1/13
 */
public class TransactionManagerTest {

    //Static single database connection manager per test suite
    static public TransactionManagerSetup TM;

    @Parameters({"database", "connectionURL"})
    @BeforeSuite
    public void beforeSuite(@Optional String database, @Optional String connectionURL)
            throws Exception {
        TM = new TransactionManagerSetup(database != null ? DatabaseProduct.valueOf(database
                .toUpperCase(Locale.CHINESE)) : DatabaseProduct.MYSQL, connectionURL);
    }

    @AfterSuite(alwaysRun = true)
    public void afterSuite() throws Exception {
        if (TM != null)
            TM.stop();
    }
}

4 基于 JPA 的 HelloWorld

4.1 POJO 类

package net.deniro.hibernate.model.helloworld;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

/**
 * @author Deniro Li
 *         2017/1/13
 */
@Entity
public class Message {
    @Id
    @GeneratedValue//自动生成 ID
    private Long id;

    private String text;

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }
}

4.2 配置持久层单元

  • 在 resources/META-INF/persistence.xml 下
  • POJO 对应的表,Hibernate 会自动建立

<persistence
        version="2.1"
        xmlns="http://xmlns.jcp.org/xml/ns/persistence"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
        http://xmlns.jcp.org/xml/ns/persistence_2_1.xsd">

    
    
    <persistence-unit name="HelloWorldPU">
                
                <jta-data-source>deniroDSjta-data-source>

                
                <class>net.deniro.hibernate.model.helloworld.Messageclass>

                
                <exclude-unlisted-classes>trueexclude-unlisted-classes>

                
                <properties>
                    
                    
                    
                    <property name="javax.persistence.schema-generation.database.action"
                              value="drop-and-create"/>

                    
                    <property name="hiberate.format_sql" value="true"/>

                    
                    <property name="hibernate.use_sql_comments" value="true"/>
                properties>
    persistence-unit>
persistence>

4.3 单元测试

package net.deniro.hibernate.example.helloworld;

import net.deniro.hibernate.env.TransactionManagerTest;
import net.deniro.hibernate.model.helloworld.Message;
import org.testng.annotations.Test;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.transaction.UserTransaction;
import java.util.List;

import static org.testng.AssertJUnit.assertEquals;

/**
 * 基于 JPA 的 HelloWorld
 *
 * @author Deniro Li
 *         2017/1/13
 */
public class HelloWorldJPA extends TransactionManagerTest {

    @Test
    public void storeLoadMessage() throws Exception {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("HelloWorldPU");

        try {
            {
                //保存
                UserTransaction tx = TM.getUserTransaction();
                tx.begin();
                EntityManager em = emf.createEntityManager();
                Message message = new Message();
                message.setText("Hello World!");
                em.persist(message);
                tx.commit();
                em.close();
            }
            {
                //查询
                UserTransaction tx = TM.getUserTransaction();
                tx.begin();
                EntityManager em = emf.createEntityManager();
                List messages = em.createQuery("select m from Message m")
                        .getResultList();
                assertEquals(messages.size(), 1);
                assertEquals(messages.get(0).getText(), "Hello World!");

                //更新
                messages.get(0).setText("Take me to your leader!");
                tx.commit();
                em.close();
            }


        } finally {
            TM.rollback();
            emf.close();
        }
    }
}

运行测试用例:

自此,我们基于 JPA 的 Hibernate 环境就搭建好啦 O(∩_∩)O~

你可能感兴趣的:(数据库,Hibernate)