构建基于 JPA 的 Hibernate 环境

来源:互联网 发布:mac抓取网页视频 编辑:程序博客网 时间:2024/06/05 00:07

——跟我一起学 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 */@Entitypublic 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~

0 0