江苏省网站备案查询/站长工具seo下载
——跟我一起学 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() {@Overridepublic 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.6org.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 suitestatic public TransactionManagerSetup TM;@Parameters({"database", "connectionURL"})@BeforeSuitepublic 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//自动生成 IDprivate 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/persistencehttp://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 {@Testpublic 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~