必須得學一學Spring 的ORM機制

擁抱心中的夢想發表於2018-03-28

小編昨天一整天都在入手一份工程程式碼,可是搞了一整天,發現最後卡在一個資料庫操作上,自己再琢磨了會,發現工程是採用Spring orm整合hibernate的方式對資料庫進行操作,但是我不會啊!我以前是學過SSH的整合,可是那些寶貴的知識都還給老師了呀!小編也是非常地無奈。好吧!既然不會,那就學學嘛!我們這就動手!

一、總體說下Spring ORM框架的結構

說到ORM,就是所謂的物件關係對映,可以簡單地理解成將java中的物件與資料庫中的表對應起來的一種模型。那麼Spring ORM到底是什麼東西呢?我們來看看Spring ORM的原始碼結構就知道了!下面我貼一張圖:

必須得學一學Spring 的ORM機制

從圖中可以看到在Spring ORM模組中預設對兩種技術提供了支援,分別是hibernate5和jpa,hibernate是一個ORM框架,jpa它是java持久化API,下面我將會重點結合hibernate來講解Spring ORM。關於這兩種技術的詳細資訊,請自行谷哥咯!

在對下面第二節的閱讀前,我想以我的理解講講我們一般是如何對資料庫進行操作的,方便我們從根本上來了解操作原理,不至於被Spring ORM中的各種類給弄矇蔽了!

一般程式運算元據庫有以下步驟:

  • 1、配置一個資料庫源(DataSource),這個很好理解,既然你要對資料庫進行操作,那麼你的資料是從哪裡來的呢?肯定是資料庫嘛!
  • 2、建立與資料庫的連結(Connection),非常好理解,你要和小編通電話,總得有個連結你我的工具吧!沒錯,那就是電話線!
  • 3、獲取會話(Session),會話就是你和小編的一次通話,通話完,上面的連結可以關閉,也可以不關閉,看心情!哈哈!
  • 4、然後就是對資料庫進行操作啦!

二、詳細講下Spring ORM重要類及其使用

下面我們對Spring ORM原始碼中的hibernate進行展開,如下圖:

必須得學一學Spring 的ORM機制

原始碼中我們重點關注幾個重要的類及介面,分別是:

  • HibernateOperations介面,該介面封裝了很多基於hibernate api的操作,該介面被HibernateTemplate實現,雖然該介面不經常使用,但是在Spring中可以用於測試。
  • HibernateTemplate類,該類實現了HibernateOperations介面,因此該類同樣擁有了很對基於hibernate的資料庫操作。但細心的小夥伴會發現,它的命名中出現了Template模板,可以推測它是一個模板類,沒錯,該類是模板方法設計模式的一個體現,關於模板方法設計模式,可以參考我之前的博文談一談我對‘模板方法’設計模式的理解(Template),該類中提供了一個模板方法,具體如下:
@Override@Nullablepublic <
T>
T execute(HibernateCallback<
T>
action) throws DataAccessException {
return doExecute(action, false);

}複製程式碼

該模板方法超級簡單,傳入一個Hibernate回撥例項,然後執行doExecute()方法。你可能會問,那這個HibernateCallback是什麼?doExecute()又是什麼?開啟HibernateCallback原始碼:

@FunctionalInterfacepublic interface HibernateCallback<
T>
{
@Nullable T doInHibernate(Session session) throws HibernateException;

}複製程式碼

哇,太簡單了,這時我會想,那既然是個介面,肯定有實現類吧!然後我找呀找呀還是沒找到。最後發現,該介面原來被@FunctionalInterface修飾,是一個函式式介面,所以我們可以用Lamada表示式來使用它!至於介面中宣告的引數SessiondoInHibernate()方法,其實道理很簡單,就是通過獲取一個會話例項(還記得你和小編的一次通電話嗎!上面第一節第三步)來做些運算元據庫的事(上面第四步),這些操作(比如資料持久化)你就可以在doInHibernate()方法中寫嘛!具體如下:

new HibernateTemplate().execute((e) ->
e.createQuery("select * from user"));
複製程式碼

當然在實際使用中我們不能直接就這麼new HibernateTemplate(),你還必須傳入一個sessionFactory的例項,再者一般例項化都是交給Spring容器去做的,不需要顯示去new,這裡只是栗子,重點在於演示HibernateCallback怎麼去使用。或者既然它是一個函式式介面,也不一定要依賴HibernateTemplate類,只要你傳入一個Session例項即可!

接下來我們看一看doExecute()方法:

@SuppressWarnings("deprecation")@Nullableprotected <
T>
T doExecute(HibernateCallback<
T>
action, boolean enforceNativeSession) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
//先斷言保證action不為空 Session session = null;
//會話物件 boolean isNew = false;
//是不是新的會話 try {
session = obtainSessionFactory().getCurrentSession();
//初始化會話物件
} catch (HibernateException ex) {
logger.debug("Could not retrieve pre-bound Hibernate session", ex);

} if (session == null) {
session = obtainSessionFactory().openSession();
session.setFlushMode(FlushMode.MANUAL);
//設定提交方式 isNew = true;

} try {
enableFilters(session);
//根據是否強制將原生Hibernate會話session暴露給回撥程式碼,預設為false,如果是true,就做一個代理 Session sessionToExpose = (enforceNativeSession || isExposeNativeSession() ? session : createSessionProxy(session));
return action.doInHibernate(sessionToExpose);

} catch (HibernateException ex) {
throw SessionFactoryUtils.convertHibernateAccessException(ex);

} catch (PersistenceException ex) {
if (ex.getCause() instanceof HibernateException) {
throw SessionFactoryUtils.convertHibernateAccessException((HibernateException) ex.getCause());

} throw ex;

} catch (RuntimeException ex) {
// Callback code threw application exception... throw ex;

} finally {
if (isNew) {
SessionFactoryUtils.closeSession(session);

} else {
disableFilters(session);

}
}
}複製程式碼
  • LocalSessionFactoryBean類,該類表示一個FactoryBean物件,用於生成所有的Session,一般我們在Spring配置檔案中配置該bean

三、來個栗子壓壓驚!

資料庫結構

必須得學一學Spring 的ORM機制

工程結構

必須得學一學Spring 的ORM機制

Student.java

package wokao666.pojo;
import java.io.Serializable;
public class Student implements Serializable {//序列化 private int id;
private String name;
private String mobile;
@Override public String toString() {
return "Student [id=" + id + ", name=" + name + ", mobile=" + mobile + "]";

} public Student(int id, String name, String mobile) {
super();
this.id = id;
this.name = name;
this.mobile = mobile;

} public Student() {
super();

} public Student(String name, String mobile) {
super();
this.name = name;
this.mobile = mobile;

} 這裡省略一大堆get/set方法
}複製程式碼

SpringORMTest.java

package wokao666.test;
import java.util.Arrays;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.orm.hibernate5.HibernateTemplate;
import wokao666.pojo.Student;
public class SpringORMTest {
public static void saveStudent(ApplicationContext context) {
HibernateTemplate template = (HibernateTemplate) context.getBean("hibernateTemplate");
//預設為只讀模式,要設定改為允許delete、save、update等操作 template.setCheckWriteOperations(false);
//注意此處一定要設定關閉,因為HibernateTemplate預設會對Session進行檢查 Student stu = new Student("0", "000000000000000");
template.save(stu);
System.out.println(Arrays.toString(template.find("from Student", null).toArray()).toString());

} public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
saveStudent(context);

}
}複製程式碼

applicationContext.xml

<
?xml version="1.0" encoding="UTF-8"?>
<
beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<
!-- 載入屬性檔案 -->
<
context:property-placeholder ignore-unresolvable="true" location="classpath*:datasource.properties" />
<
!-- 配置資料來源 -->
<
bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<
property name="driverClassName" value="${spring.datasource.driverClassName
}
"
/>
<
property name="url" value="${spring.datasource.url
}
"
/>
<
property name="username" value="${spring.datasource.username
}
"
/>
<
property name="password" value="${spring.datasource.password
}
"
/>
<
/bean>
<
!-- 配置session工廠 -->
<
bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<
property name="dataSource" ref="dataSource" />
<
property name="hibernateProperties">
<
props>
<
prop key="hibernate.dialect">
org.hibernate.dialect.MySQLDialect<
/prop>
<
prop key="hibernate.show_sql">
true<
/prop>
<
/props>
<
/property>
<
property name="mappingResources">
<
list>
<
value>
./student.hbm.xml<
/value>
<
/list>
<
/property>
<
/bean>
<
!-- 最後配置 HibernateTemplate bean-->
<
bean id="hibernateTemplate" class="org.springframework.orm.hibernate5.HibernateTemplate">
<
property name="sessionFactory" ref="sessionFactory">
<
/property>
<
/bean>
<
/beans>
複製程式碼

datasource.properties

spring.datasource.url = jdbc:mysql://localhost:3306/demo_db?useUnicode=true&
characterEncoding=UTF-8&
serverTimezone=Asia/Shanghai&
useSSL=truespring.datasource.username=rootspring.datasource.password=rootspring.datasource.driverClassName = com.mysql.jdbc.Driver 複製程式碼

student.hbm.xml

<
?xml version="1.0" encoding="UTF-8"?>
<
!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN""http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<
hibernate-mapping>
<
class name="wokao666.pojo.Student" table="user">
<
id name="id" type="java.lang.Integer">
<
column name="Id" />
<
generator class="identity" />
<
/id>
<
property name="mobile" type="java.lang.String" column="MOBILE" length="100" />
<
property name="name" type="java.lang.String" column="NAME" length="100" />
<
/class>
<
/hibernate-mapping>
複製程式碼

build.gradle

apply plugin: 'java'repositories { 
jcenter()
}dependencies {
compile 'org.slf4j:slf4j-api:1.7.21' testCompile 'junit:junit:4.12' compile 'org.springframework:spring-context:5.0.4.RELEASE' compile 'org.springframework:spring-orm:5.0.4.RELEASE' compile group: 'mysql', name: 'mysql-connector-java', version: '6.0.4' compile group: 'org.hibernate', name: 'hibernate-core', version: '5.2.9.Final' compile group: 'cglib', name: 'cglib', version: '3.2.4' compile group: 'org.springframework', name: 'spring-tx', version: '5.0.0.RELEASE' compile group: 'org.aspectj', name: 'aspectjweaver', version: '1.8.8'
}複製程式碼

四、其他特性總結(具體可以自行閱讀原始碼)

  • 1、HibernateTemplate自己管理事務,不需要我們來對事務進行管理,不需要我們來開啟Session,關閉Session

  • 2、在HibernateTemplate中,還可以對其注入過濾器,所有的過濾器都會在你執行一個操作前開啟,並在操作完成之後完全結束

  • 3、如果設定exposeNativeSessiontrue,那麼一個session代理將會被返回,同時抑制close方法被呼叫,同時開啟查詢快取和事務超時等功能!

  • 4、預設HibernateTemplate為只讀事務,我們如果想對其進行寫操作,必須取消session檢查機制,呼叫setCheckWriteOperation方法

  • 5、你還可以設定查詢快取,並指定查詢快取區域

來源:https://juejin.im/post/5ab999c26fb9a028c97a1002

相關文章