Hibernate使用及源码分析(一)
本篇文章主要通过hibernate初级使用分析一下源码,只是给初学者一点小小的建议,不喜勿喷,谢谢!
- hibernate环境搭建
- 简单使用
- 源码走读
一 hibernate环境搭建
这里直接介绍使用maven搭建
首先引入maven相关依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- 添加Hibernate依赖 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>3.6.10.Final</version>
</dependency>
<!-- 添加Log4J依赖 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.6.4</version>
</dependency>
<!-- 添加javassist -->
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.0.GA</version>
</dependency>
<!-- mysql数据库的驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
</dependencies>
二 简单使用
配置hibernate主配置文件
<?xml version='1.0' encoding='utf-8'?>
<!--表明解析本XML文件的DTD文档位置,DTD是DocumentType Definition 的缩写,
即文档类型的定义,XML解析器使用DTD文档来检查XML文件的合法性。
hibernate.sourceforge.net/hibernate-configuration-3.0dtd可以在
Hibernate3.1.3软件包中的src\org\hibernate目录中找到此文件-->
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<!--表明以下的配置是针对session-factory配置的,SessionFactory是
Hibernate中的一个类,这个类主要负责保存HIbernate的配置信息,以及对Session的
操作-->
<session-factory>
<!--配置数据库的驱动程序,Hibernate在连接数据库时,需要用到数据库的驱
动程序-->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<!--设置数据库的连接url:jdbc:mysql://localhost/hibernate,其中localhost表示mysql服务器名称,此处为本机,hibernate是数据库名-->
<property name="hibernate.connection.url">jdbc:mysql://localhost/hibernate</property>
<!--连接数据库是用户名-->
<property name="hibernate.connection.username">root</property>
<!--连接数据库是密码-->
<property name="hibernate.connection.password">root</property>
<!--数据库连接池的大小-->
<property name="hibernate.connection.pool.size">20</property>
<!--是否在后台显示Hibernate用到的SQL语句,开发时设置为true,便于差错,程序运行时可以在Eclipse的控制台显示Hibernate的执行Sql语句。项目部署后可
以设置为false,提高运行效率-->
<property name="show_sql">true</property>
<!--jdbc.fetch_size是指Hibernate每次从数据库中取出并放到JDBC的Statement中的记录条数。FetchSize设的越大,读数据库的次数越少,
速度越快,Fetch Size越小,读数据库的次数越多,速度越慢-->
<property name="jdbc.fetch_size">50</property>
<!--jdbc.batch_size是指Hibernate批量插入,删除和更新时每次操作的记录数。BatchSize越大,
批量操作的向数据库发送Sql的次数越少,速度就越快,同样耗用内存就越大-->
<property name="jdbc.batch_size">23</property>
<!--jdbc.use_scrollable_resultset是否允许Hibernate用JDBC的可滚动的结果集。对分页的结果集。对分页时的设置非常有帮助-->
<property name="jdbc.use_scrollable_resultset">false</property>
<!--connection.useUnicode连接数据库时是否使用Unicode编码-->
<!--<property name="Connection.useUnicode">true</property>-->
<!--connection.characterEncoding连接数据库时数据的传输字符集编码方式,最好设置为gbk,用gb2312有的字符不全-->
<!--<property name="connection.characterEncoding">gbk</property>-->
<!--hibernate.dialect 只是Hibernate使用的数据库方言,就是要用Hibernate连接那种类型的数据库服务器。-->
<property name="hibernate.dialect">
org.hibernate.dialect.MySQLDialect
</property>
<!--是否自动创建数据库表 他主要有一下几个值:
validate:当sessionFactory创建时,自动验证或者schema定义导入数据库。
create:每次启动都drop掉原来的schema,创建新的。
create-drop:当sessionFactory明确关闭时,drop掉schema。
update(常用):如果没有schema就创建,有就更新。
-->
<property name="hbm2ddl.auto">update</property>
<!--配置此处 sessionFactory.getCurrentSession()可以完成一系列的工作,当调用时,
hibernate将session绑定到当前线程,事务结束后,hibernate
将session从当前线程中释放,并且关闭session。当再次调用getCurrentSession
()时,将得到一个新的session,并重新开始这一系列工作。-->
<property name="current_session_context_class">thread</property>
<!--<property name="hibernate.generate_statistics">true</property>-->
<!--<property name="hibernate.connection.autocommit">true</property>-->
<mapping resource="hibernate-hbm/User.hbm.xml"/>
</session-factory>
</hibernate-configuration>
创建一个实体类
public class User {
public static final String TABLE_NAME = "t_user";
private Long id;
private String name;
private Integer age;
public User() {
}
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
然后给实体类写一个ORM映射文件
<!-- 一样,这个都可以在hibernate-core包里面找到dtd头部约束 -->
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.test.entity">
<class name="User" table="t_user">
<id name="id">
<generator class="native"/>
</id>
<property name="name" column="t_name" type="java.lang.String"/>
<property name="age" column="t_age" type="java.lang.Integer"/>
</class>
</hibernate-mapping>
最后开始测试,这里就使用了最简单的save方法来方便阅读源码
@Test
public void testSave() throws Exception {
Configuration configuration = new Configuration().configure();
SessionFactory factory = configuration.buildSessionFactory();
Session session = factory.openSession();
Transaction transaction = session.beginTransaction();
session.save(new User("里斯", 18));
transaction.commit();
session.close();
factory.close();
}
三 根据测试方法阅读源码。
1,创建Configuration对象
其中new Configuration()方法主要是初始化Configuration对象中的一些成员变量。
这上面一个很重要的类Environment里面都是我们在主配置文件里面配置的属性的key值。
然后通过configure来读取配置文件。
sax解析配置文件。
把session-factory节点里面的属性添加给Configuration
通过addProperties()方法将配置文件中的property节点的属性都加载到properties集合中。
从这个方法也可以看出来我们即使不加hibernate前缀也没有问题。
通过parseSessionFactory()方法来读取配置文件中的mapping映射文件等等。
当然还有监听器,event等等,后面再分析。
从读取映射文件的方法可以看出,我们是有多种配置映射文件的方式的。
2.buildSessionFactory()创建session工厂
这里主要是创建了一个SessionFactoryImpl对象。
在他的构造方法里面会进行建表。
Map<String,ClassMetadata> classMeta = new HashMap<String,ClassMetadata>();
classes = cfg.getClassMappings();
while ( classes.hasNext() ) {
final PersistentClass model = (PersistentClass) classes.next();
//根据mapping拼接建表语句
model.prepareTemporaryTables( mapping, settings.getDialect() );
final String cacheRegionName = cacheRegionPrefix + model.getRootClass().getCacheRegionName();
// cache region is defined by the root-class in the hierarchy...
EntityRegionAccessStrategy accessStrategy = ( EntityRegionAccessStrategy ) entityAccessStrategies.get( cacheRegionName );
if ( accessStrategy == null && settings.isSecondLevelCacheEnabled() ) {
final AccessType accessType = AccessType.parse( model.getCacheConcurrencyStrategy() );
if ( accessType != null ) {
log.trace( "Building cache for entity data [" + model.getEntityName() + "]" );
EntityRegion entityRegion = settings.getRegionFactory().buildEntityRegion( cacheRegionName, properties, CacheDataDescriptionImpl.decode( model ) );
accessStrategy = entityRegion.buildAccessStrategy( accessType );
entityAccessStrategies.put( cacheRegionName, accessStrategy );
allCacheRegions.put( cacheRegionName, entityRegion );
}
}
。。。。
//这些就是对应我们在配置文件中配置的建表方式
if ( settings.isAutoCreateSchema() ) {
new SchemaExport( cfg, settings ).create( false, true );
}
//创建表
if ( settings.isAutoUpdateSchema() ) {
new SchemaUpdate( cfg, settings ).execute( false, true );
}
if ( settings.isAutoValidateSchema() ) {
new SchemaValidator( cfg, settings ).validate();
}
if ( settings.isAutoDropSchema() ) {
schemaExport = new SchemaExport( cfg, settings );
}
3 打开事务
public Transaction beginTransaction() throws HibernateException {
errorIfClosed();
if ( rootSession != null ) {
// todo : should seriously consider not allowing a txn to begin from a child session
// can always route the request to the root session...
log.warn( "Transaction started on non-root session" );
}
Transaction result = getTransaction();
result.begin();
return result;
}
4 save()方法
save方法会创建一个saveorupdate对象,通过saveeventlistener回调执行保存操作。
也就是调用DefaultSaveOrUpdateEventListener的onSaveOrUpdate()方法。
public void onSaveOrUpdate(SaveOrUpdateEvent event) {
final SessionImplementor source = event.getSession();
final Object object = event.getObject();
final Serializable requestedId = event.getRequestedId();
if ( reassociateIfUninitializedProxy( object, source ) ) {
log.trace( "reassociated uninitialized proxy" );
// an uninitialized proxy, noop, don't even need to
// return an id, since it is never a save()
}
else {
//initialize properties of the event:
final Object entity = source.getPersistenceContext().unproxyAndReassociate( object );
event.setEntity( entity );
event.setEntry( source.getPersistenceContext().getEntry( entity ) );
//return the id in the event object
event.setResultId( performSaveOrUpdate( event ) );
}
}
最后调用到AbstractSaveEventListener.java的performSaveOrReplicate方法进行insert语句
if ( useIdentityColumn ) {
EntityIdentityInsertAction insert = new EntityIdentityInsertAction(
values, entity, persister, source, shouldDelayIdentityInserts
);
if ( !shouldDelayIdentityInserts ) {
log.debug( "executing identity-insert immediately" );
source.getActionQueue().execute( insert );
id = insert.getGeneratedId();
key = new EntityKey( id, persister, source.getEntityMode() );
source.getPersistenceContext().checkUniqueness( key, entity );
}
else {
log.debug( "delaying identity-insert due to no transaction in progress" );
source.getActionQueue().addAction( insert );
key = insert.getDelayedEntityKey();
}
}
5 最后提交事务
6 关于hibernate不开启事务提交就无法保存等操作的原因分析
还是使用上面save的案例。当我们执行save的时候,AbstractSaveEventListener在调用source.getActionQueue().execute( insert )方法的时候最终调用到AbstractBatcher的prepareStatement方法。
return getPreparedStatement(
connectionManager.getConnection(),
sql,
false,
getGeneratedKeys,
null,
null,
false
);
这个里面会通过ConnectionManager获取connection。
/**
* Pysically opens a JDBC Connection.
*
* @throws HibernateException
*/
private void openConnection() throws HibernateException {
if ( connection != null ) {
return;
}
log.debug("opening JDBC connection");
try {
connection = factory.getConnectionProvider().getConnection();
}
catch (SQLException sqle) {
throw JDBCExceptionHelper.convert(
factory.getSQLExceptionConverter(),
sqle,
"Cannot open connection"
);
}
callback.connectionOpened(); // register synch; stats.connect()
}
上面最终会调用到DriverManagerConnectionProvider里面来获取Connection对象。
public Connection getConnection() throws SQLException {
if ( log.isTraceEnabled() ) log.trace( "total checked-out connections: " + checkedOut );
synchronized (pool) {
if ( !pool.isEmpty() ) {
int last = pool.size() - 1;
if ( log.isTraceEnabled() ) {
log.trace("using pooled JDBC connection, pool size: " + last);
checkedOut++;
}
Connection pooled = (Connection) pool.remove(last);
if (isolation!=null) pooled.setTransactionIsolation( isolation.intValue() );
if ( pooled.getAutoCommit()!=autocommit ) pooled.setAutoCommit(autocommit);
return pooled;
}
}
log.debug("opening new JDBC connection");
Connection conn = DriverManager.getConnection(url, connectionProps);
if (isolation!=null) conn.setTransactionIsolation( isolation.intValue() );
if ( conn.getAutoCommit()!=autocommit ) conn.setAutoCommit(autocommit);
if ( log.isDebugEnabled() ) {
log.debug( "created connection to: " + url + ", Isolation Level: " + conn.getTransactionIsolation() );
}
if ( log.isTraceEnabled() ) checkedOut++;
return conn;
}
我们可以看到这里面会通过setAutoCommint()方法设置事务提交。所以我们需要查看这个autocommit是哪里控制的。最后我们发现是在DriverManagerConnectionProvider的configure(Properties props)方法里面进行赋值的
autocommit = PropertiesHelper.getBoolean(Environment.AUTOCOMMIT, props);
去Environment里面查看这个常量
/**
* JDBC autocommit mode
*/
public static final String AUTOCOMMIT ="hibernate.connection.autocommit";
发现只有配置了hibernate.connection.autocommit为true,hibernate才会自动提交,否则默认是不会提交事务的,必须要我们自己手动提交事务。
好了,今天就到这里,谢谢大家!!!