构建基于 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-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>

        <!-- JPA 标准 API-->
        <hibernate.jpa21.api.version>1.0.0.Final</hibernate.jpa21.api.version>

        <!-- Hibernate 实现-->
        <hibernate.version>5.1.0.Final</hibernate.version>

        <!-- Bean 验证器 标准 API -->
        <validation.api.version>1.1.0.Final</validation.api.version>

        <!-- Hibernate 验证器实现-->
        <hibernate.validator.version>5.2.1.Final</hibernate.validator.version>
        <javax-el.version>3.0.1-b04</javax-el.version>

        <!-- 日志-->
        <slf4j.impl.version>1.6.1</slf4j.impl.version>

        <!-- TestNG 单元测试-->
        <testing.version>6.8.7</testing.version>

        <!-- Jav SE 环境下使用 Bitronix (为JTA 事务管理器提供数据库连接池)-->
        <btm.version>2.1.3</btm.version>

    </properties>

    <!-- 依赖库-->
    <dependencies>

        <!-- TestNG 单元测试-->
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>${testing.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>junit</groupId>
                    <artifactId>junit</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- slf4j 日志-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-jdk14</artifactId>
            <version>${slf4j.impl.version}</version>
        </dependency>

        <!-- Bitronix 数据库连接池 -->
        <dependency>
            <groupId>org.codehaus.btm</groupId>
            <artifactId>btm</artifactId>
            <version>${btm.version}</version>
        </dependency>

        <!-- Hibernate(JPA实现)-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hibernate.version}</version>
        </dependency>

        <!-- Bean 验证器 API 与实现-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>${hibernate.validator.version}</version>
        </dependency>

        <!-- EL -->
        <dependency>
            <groupId>javax.el</groupId>
            <artifactId>javax.el-api</artifactId>
            <version>${javax-el.version}</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>javax.el</artifactId>
            <version>${javax-el.version}</version>
        </dependency>

        <!-- Hibernate 审计-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-envers</artifactId>
            <version>${hibernate.version}</version>
        </dependency>

        <!-- EHCache 作为二级缓存-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-ehcache</artifactId>
            <version>${hibernate.version}</version>
        </dependency>


        <!-- mysql driver -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.34</version>
        </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 会自动建立
<?xml version="1.0" encoding="UTF-8"?>
<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>deniroDS</jta-data-source>

                <!-- 需要持久化的类-->
                <class>net.deniro.hibernate.model.helloworld.Message</class>

                <!-- 是否扫描 classpath 路径下的映射类,并自动加入这个持久层单元-->
                <exclude-unlisted-classes>true</exclude-unlisted-classes>

                <!-- 设置属性-->
                <properties>
                    <!-- 删除并重建数据库(schema) -->
                    <!-- 这个属性设置后,当 JPA 引擎启动时会删除并重建数据库-->
                    <!-- 一般用于项目的自动化测试,因为测试需要一个干净的数据库环境-->
                    <property name="javax.persistence.schema-generation.database.action"
                              value="drop-and-create"/>

                    <!-- 格式化输出 SQL(如果有输出日志)-->
                    <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<Message> 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~

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值