Hibernate使用及原始碼分析(一)
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才會自動提交,否則預設是不會提交事務的,必須要我們自己手動提交事務。
好了,今天就到這裡,謝謝大家!!!