必須得學一學Spring 的ORM機制

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

1、整體說下Spring ORM框架的結構

說到ORM,就是所謂的對象關係映射,能夠簡單地理解成將java中的對象與數據庫中的表對應起來的一種模型。那麼Spring ORM究竟是什麼東西呢?咱們來看看Spring ORM的源碼結構就知道了!下面我貼一張圖:mysql

從圖中能夠看到在Spring ORM模塊中默認對兩種技術提供了支持,分別是hibernate5和jpa,hibernate是一個ORM框架,jpa它是java持久化API,下面我將會重點結合hibernate來說解Spring ORM。關於這兩種技術的詳細信息,請自行谷哥咯!spring

在對下面第二節的閱讀前,我想以個人理解講講咱們通常是如何對數據庫進行操做的,方便咱們從根本上來了解操做原理,不至於被Spring ORM中的各類類給弄矇蔽了!sql

通常程序操做數據庫有如下步驟:數據庫

  • 一、配置一個數據庫源(DataSource),這個很好理解,既然你要對數據庫進行操做,那麼你的數據是從哪裏來的呢?確定是數據庫嘛!
  • 二、創建與數據庫的連接(Connection),很是好理解,你要和小編通電話,總得有個連接你個人工具吧!沒錯,那就是電話線!
  • 三、獲取會話(Session),會話就是你和小編的一次通話,通話完,上面的連接能夠關閉,也能夠不關閉,看心情!哈哈!
  • 四、而後就是對數據庫進行操做啦!

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

下面咱們對Spring ORM源碼中的hibernate進行展開,以下圖:設計模式

源碼中咱們重點關注幾個重要的類及接口,分別是:api

  • HibernateOperations接口,該接口封裝了不少基於hibernate api的操做,該接口被HibernateTemplate實現,雖然該接口不常用,可是在Spring中能夠用於測試。
  • HibernateTemplate類,該類實現了HibernateOperations接口,所以該類一樣擁有了很對基於hibernate的數據庫操做。但細心的小夥伴會發現,它的命名中出現了Template模板,能夠推測它是一個模板類,沒錯,該類是模板方法設計模式的一個體現,關於模板方法設計模式,能夠參考我以前的博文談一談我對‘模板方法’設計模式的理解(Template),該類中提供了一個模板方法,具體以下:
@Override
@Nullable
public <T> T execute(HibernateCallback<T> action) throws DataAccessException {
	return doExecute(action, false);
}
複製代碼

該模板方法超級簡單,傳入一個Hibernate回調實例,而後執行doExecute()方法。你可能會問,那這個HibernateCallback是什麼?doExecute()又是什麼? 打開HibernateCallback源碼:緩存

@FunctionalInterface
public interface HibernateCallback<T> {
	@Nullable
	T doInHibernate(Session session) throws HibernateException;
}
複製代碼

哇,太簡單了,這時我會想,那既然是個接口,確定有實現類吧!而後我找呀找呀仍是沒找到。最後發現,該接口原來被@FunctionalInterface修飾,是一個函數式接口,因此咱們能夠用Lamada表達式來使用它!至於接口中聲明的參數SessiondoInHibernate()方法,其實道理很簡單,就是經過獲取一個會話實例(還記得你和小編的一次通電話嗎!上面第一節第三步)來作些操做數據庫的事(上面第四步),這些操做(好比數據持久化)你就能夠在doInHibernate()方法中寫嘛!具體以下:bash

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

固然在實際使用中咱們不能直接就這麼new HibernateTemplate(),你還必須傳入一個sessionFactory的實例,再者通常實例化都是交給Spring容器去作的,不須要顯示去new,這裏只是栗子,重點在於演示HibernateCallback怎麼去使用。或者既然它是一個函數式接口,也不必定要依賴HibernateTemplate類,只要你傳入一個Session實例便可!session

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

@SuppressWarnings("deprecation")
@Nullable
protected <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

3、來個栗子壓壓驚!

數據庫結構

工程結構

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=true
spring.datasource.username=root
spring.datasource.password=root
spring.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'
}
複製代碼

4、其餘特性總結(具體能夠自行閱讀源代碼)

  • 一、HibernateTemplate本身管理事務,不須要咱們來對事務進行管理,不須要咱們來打開Session,關閉Session

  • 二、在HibernateTemplate中,還能夠對其注入過濾器,全部的過濾器都會在你執行一個操做前開啓,並在操做完成以後徹底結束

  • 三、若是設置exposeNativeSessiontrue,那麼一個session代理將會被返回,同時抑制close方法被調用,同時開啓查詢緩存和事務超時等功能!

  • 四、默認HibernateTemplate爲只讀事務,咱們若是想對其進行寫操做,必須取消session檢查機制,調用setCheckWriteOperation方法

  • 五、你還能夠設置查詢緩存,並指定查詢緩存區域

相關文章
相關標籤/搜索