——跟我一起学 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~