hibernate(jpa)+spring

項目中用到了hibernate,抽個時間記錄下(前面有個簡陋的介紹spring+redis+hibernate,此篇對hibernate有個更爲詳細的使用).

Hibernate是一種ORM(Object Relational Mapping)映射工具,能夠建立面向對象的域模型和關係數據模型之間的映射

Object Mapping(Hibernate) Relational

面向對象領域 關係數據庫領域

類:Class 表:Table

對象:內存中 表:記錄

Java類型 字段:類型

  Hibernate通過操作實體對象來操作數據庫。

hibernate是一種持久層的技術,而相關持久層技術

項目中mybatis與hibernate都有接觸,當有一套成型的hibernate Dao,開發相對於mybatis要快速很多.mybatis與hibernate各有優劣,但個人感覺hibernate更貼切與對象這個概念,尤其是實現了JPA之後,實現各個對象之間的關聯關係顯得更加方便,然而對於一個摸索中的項目,往往一對多,多對多等關聯關係上顯得十分慎用,業務越複雜的時候,關聯關係越複雜,級聯級別越多,一個錯誤的刪除往往會引發一系列的連鎖反應,況且,業務中假刪除應用的更多,而hibernate中沒有直接支持的假刪除.

偷了個懶,沒有用jdk1.8版本測試,spring4.3.4+hibernate5.1.3+jdk1.7+mysql5.相對於hibernate臃腫的xml文件配置,個人更傾向於使用JPA註解開發(模板用習慣了,這裏僅僅就用了JPA orm部分,對象的相關操作使用hibernateTemplate,而並沒有使用JPA的entitymanager)

pom.xml

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<hibernate.version>5.1.3.Final</hibernate.version>
		<spring.version>4.3.4.RELEASE</spring.version>
	</properties>
	<dependencies>

	<!--spring整合redis,模板.工廠.連接池等 -->
	<dependency>
		<groupId>org.springframework.data</groupId>
		<artifactId>spring-data-redis</artifactId>
		<version>1.8.0.RELEASE</version>
	</dependency>
	
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<version>4.12</version>
		<scope>test</scope>
	</dependency>

	<!--spring測試 -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-test</artifactId>
		<version>${spring.version}</version>
	</dependency>

	<!--redis客戶端,連接redis -->
	<dependency>
		<groupId>redis.clients</groupId>
		<artifactId>jedis</artifactId>
		<version>2.9.0</version>
	</dependency>

	<!--數據源 -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.18</version>
	</dependency>
	<dependency>
		<groupId>c3p0</groupId>
		<artifactId>c3p0</artifactId>
		<version>0.9.1.2</version>
	</dependency>

	<!--hibernate所需依賴 -->
	<dependency>
		<groupId>org.hibernate</groupId>
		<artifactId>hibernate-core</artifactId>
		<version>${hibernate.version}</version>
	</dependency>

	<!-- for JPA, use hibernate-entitymanager instead of hibernate-core -->
	<dependency>
		<groupId>org.hibernate</groupId>
		<artifactId>hibernate-entitymanager</artifactId>
		<version>${hibernate.version}</version>
	</dependency>

	<!--使用hibernate jpa 依賴-->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-orm</artifactId>
		<version>${spring.version}</version>
	</dependency>

	<!--hibernate log4j依賴-->
	<dependency>
		<groupId>org.apache.logging.log4j</groupId>
		<artifactId>log4j-api</artifactId>
		<version>2.6.2</version>
	</dependency>
	</dependencies>

spring-hiberlate.xml

<?xml version="1.0" encoding="UTF-8"?>

<!--ignore-unresolvable=“true" 設置true找不到不會報錯,默認false -->
<!-- <context:property-placeholder location="classpath:redis.properties" 
	ignore-unresolvable="false"/> -->
<!-- 下面的變量從配置文件裏面拿出來 -->
<bean id="propertyPlaceholderConfigurer"
	class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
	<property name="locations">
		<list>
			<value>classpath:jdbc.properties</value>
		</list>
	</property>
</bean>

<!-- 數據源 -->
<bean id="C3P0DataSourceFather" class="com.mchange.v2.c3p0.ComboPooledDataSource"
	destroy-method="close" abstract="true">
	<!--設置數據庫連接 -->
	<property name="driverClass" value="com.mysql.jdbc.Driver" />
	<property name="autoCommitOnClose" value="false" />
	<!-- 當連接池中的連接耗盡的時候c3p0一次同時獲取的連接數。Default: 3 -->
	<property name="acquireIncrement" value="2" />
	<!-- 檢測pool內的連接是否正常,此參數就是Task運行的頻率 -->
	<property name="idleConnectionTestPeriod" value="60" />
	<!-- true表示每次把連接checkin到pool裏的時候測試其有效性,異步操作,會造成至少多一倍的數據庫調用 -->
	<property name="testConnectionOnCheckin" value="false" />
	<!-- 定義在從數據庫獲取新連接失敗後重復嘗試的次數。Default: 30 -->
	<property name="acquireRetryAttempts" value="600" />
	<!-- 兩次連接中間隔時間,單位毫秒。Default: 1000 -->
	<property name="acquireRetryDelay" value="1000" />
	<!-- 當連接池用完時客戶端調用getConnection()後等待獲取新連接的時間,超時後將拋出SQLException,如設爲0則無限期等待。單位毫秒。Default:0 -->
	<property name="checkoutTimeout" value="10000" />
	<property name="maxStatements" value="0" />
	<!-- 初始化時獲取三個連接,取值應在minPoolSize與maxPoolSize之間。 Default: 3 -->
	<property name="initialPoolSize" value="20" />
	<!-- 最小連接數 -->
	<property name="minPoolSize" value="3" />
	<!-- 連接池中保留的最大連接數。Default: 15 -->
	<property name="maxPoolSize" value="20" />
	<!-- 最大空閒時間,60秒內未使用則連接被丟棄。若爲0則永不丟棄。Default: 0 -->
	<property name="maxIdleTime" value="60" />
	<!-- How long to hang on to excess unused connections after traffic spike -->
	<property name="maxIdleTimeExcessConnections" value="600" />
</bean>

<!-- 主數據庫 -->
<bean id="dataSource" parent="C3P0DataSourceFather">
	<property name="jdbcUrl" value="${db.jdbcUrl}" />
	<property name="user" value="${db.user}" />
	<property name="password" value="${db.password}" />
</bean>

<!-- 主數據庫 sessionFactory -->
<bean id="sessionFactory"
	class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
	<property name="dataSource" ref="dataSource" />
	<property name="packagesToScan">
		<list>
			<!--model,對象與數據庫的之間的印射關係 -->
			<value>spring.hibernate.model</value>
		</list>
	</property>
	<property name="hibernateProperties">
		<props><!--數據庫方言,對於不同數據庫選用不同的方言(mysql切換oracle十分方便) -->
			<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop><!--開發調試使用 --><!--控制檯打印sql語句 -->
			<prop key="hibernate.show_sql">true</prop><!--格式化語句 -->
			<prop key="hibernate.format_sql">true</prop><!--如果開啓, Hibernate將在SQL中生成有助於調試的註釋信息, 默認值爲false -->
			<prop key="hibernate.use_sql_commants">false</prop><!--自動建表策略,create,update,create-drop,validate,運行時期此屬性不建議設置 --><!-- <prop key="hibernate.hbm2ddl.auto">update</prop> --><!--批量處理 --><!--Hibernate每次從數據庫中取出並放到JDBC的Statement中的記錄條數。Fetch 
				Size設的越大,讀數據庫的次數越少,速度越快,Fetch Size越小,讀數據庫的次數越多,速度越慢 -->
			<prop key="hibernate.jdbc.fetch_size">50</prop><!--Hibernate批量插入,刪除和更新時每次操作的記錄數。Batch Size越大,批量操作的向數據庫發送Sql的次數越少,速度就越快,同樣耗用內存就越大 -->
			<prop key="hibernate.jdbc.batch_size">50</prop><!--是否允許Hibernate用JDBC的可滾動的結果集。對分頁的結果集。對分頁時的設置非常有幫助 -->
			<prop key="hibernate.jdbc.use_scrollable_resultset">false</prop>
		</props>
	</property>
</bean><!--開啓註解 -->
<tx:annotation-driven transaction-manager="transactionManager" /> <!--掃描 -->
<context:component-scan base-package="spring" /> <!--事務 -->
<bean id="transactionManager"
	class="org.springframework.orm.hibernate5.HibernateTransactionManager">
	<property name="sessionFactory" ref="sessionFactory" />
</bean><!--hibernate模板 -->
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate5.HibernateTemplate">
	<property name="sessionFactory" ref="sessionFactory" />
</bean>

hibernate相關參數配置(hibernate參數配置)

定義一些model

baseEntity

package spring.hibernate.model;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@MappedSuperclass
public class BaseEntity {
	
	/** id */
	protected Integer id;
	/** 創建日期 */
	protected Date createTime = new Date();

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	@Column(name = "create_time", nullable = false, updatable = false)
	@Temporal(TemporalType.TIMESTAMP)
	public Date getCreateTime() {
		return createTime;
	}

	public void setCreateTime(Date createTime) {
		this.createTime = createTime;
	}

}
student

package spring.hibernate.model;

import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;

@Entity
@Table(name="student")
public class Student extends BaseEntity{
	private String name;
	private	School school;
	private List<Teacher> teachers;
	private IDcard iDcard;
	
	@Column(name="name")
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	@ManyToOne(fetch=FetchType.LAZY)
	@JoinColumn(name="school_id")
	public School getSchool() {
		return school;
	}
	public void setSchool(School school) {
		this.school = school;
	}
	
	@ManyToMany(mappedBy = "students", fetch = FetchType.LAZY)
	public List<Teacher> getTeachers() {
		return teachers;
	}
	public void setTeachers(List<Teacher> teachers) {
		this.teachers = teachers;
	}
	@OneToOne
	public IDcard getiDcard() {
		return iDcard;
	}
	public void setiDcard(IDcard iDcard) {
		this.iDcard = iDcard;
	}
}
school

package spring.hibernate.model;

import java.util.List;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;
import javax.persistence.Table;
@Entity
@Table(name="school")
public class School extends BaseEntity{
	private String name;
	List<Student> students;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	@OneToMany(fetch=FetchType.LAZY,mappedBy="school")
	public List<Student> getStudents() {
		return students;
	}
	public void setStudents(List<Student> students) {
		this.students = students;
	}
	
	
}
teacher

package spring.hibernate.model;

import java.util.List;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
@Entity
@Table(name="teacher")
public class Teacher extends BaseEntity{
	private String name;
	private List<Student> students;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	@ManyToMany(fetch = FetchType.LAZY)
	@JoinTable(name = "teacher_student", joinColumns = @JoinColumn(name = "teacher_id"), inverseJoinColumns = @JoinColumn(name = "student_id"))
	public List<Student> getStudents() {
		return students;
	}
	public void setStudents(List<Student> students) {
		this.students = students;
	}
	
	
}
idcard

package spring.hibernate.model;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToOne;
import javax.persistence.Table;
@Entity
@Table(name="id_card")
public class IDcard extends BaseEntity{
	private Student student;
	
	@OneToOne(fetch=FetchType.LAZY,mappedBy="iDcard")
	public Student getStudent() {
		return student;
	}

	public void setStudent(Student student) {
		this.student = student;
	}
	
}
幾個model之間的聯繫

student-school 多對一
student-idcard 一對一
sutdent-teacher 多對多

jpa相關注解解釋:

@MappedSuperclass : 標註超類,不參與印射,但其屬性將被子類集成且與數據庫印射
@Id主鍵標識,對標識@Entity的類必須有主鍵(多對多關係表沒有)
@GeneratedValue : 主鍵生成策略, table(表主鍵),sequence(序列主鍵),identity(自增主鍵),auto(程序主鍵)
@Column : 屬性映射,model的property與數據庫的映射表達,可爲空,默認的映射相關屬性
name : 映射數據庫列名,根據命名策略(可設置,默認屬性名)生成
unique : 是否唯一,default false
nullable : 是否可以爲空,default true
insertable :是否允許插入,default true
updatable :是否允許被更新,default true
columnDefinition :列定義(比如設置一些默認值什麼的)
length: 默認長度255
precision:針對decimal默認精度,default 0

@Temporal :映射時間格式-空(不寫此註解),默認TIMESTAMP(對應數據庫date_time,YYYY-MM-DD HH:MM:SS),有三種可選擇TIMESTAMP(date_time YYYY-MM-DD),DATE(date),Time(time HH:MM:SS),此註解作用於添加與查詢,值得注意的是,你設置爲Date,存入是時間精確到天,實際時間爲YYYY-MM-DD 00:00:00,此外順便提一下datetime與timestamp的區別,datetime佔用8字節,時間範圍更大(1000年-9999年),而timestamp佔用4字節,時間範圍相對較小(1970年-2038年).
此外有一點需要注意,mysql默認是不區分大小寫,所以name大小寫都可(個人習慣於數據庫保持一致,java中這個屬於常量範圍,一般用大寫標識)


@Entity : 表明映射對象
@Table: table相關信息,可不標註
@OneToOne :一對一,相關屬性
targetEntity:關聯類,一般無需設置,指的就是成員本身
cascade : 級聯,all(所有),persist(保存),merge(合併),remove(刪除),refresh(刷新),detach(分離)
fetch: 默認eager-立即加載,lazy-延遲加載(立即加載與延遲加載的區別是,立即加載擁有關聯對象中全部信息,延遲加載只有一個id)
optional:關聯關係是否可以爲空,默認爲true
mappedBy:關係維護方,在關聯關係中,OneToOne 關係是允許雙方維護的,這裏指的是被維護方在維護方的屬性名,標註在被維護方.@onetomany 只能有多的一方維護.

@manytomany可以雙方維護,注意分清維護與級聯直接的區別,一個針對關係(只操作關係),一個是針對類之間關聯,比如解除關係,記錄還在,但級聯刪除,記錄全刪.

@JoinColumn 映射關聯類(@column 不能與@onetoone @manytonone @manytomany一起,一般配合@joincolumn使用)
name : 被關聯類映射數據列名
referencedColumnName : 映射關聯類的列名(默認空)
foreignKey: 關聯主鍵,referencedColumnName指定,就無效
unique,nullable,insertable,updatable,columnDefinition與@column功能一樣.
@manyToone : targetEntity,cascade,fetch,optional 功能與@onetoone中相似

@ManyToMany : targetEntity , cascade, fetch,mappedBy 功能與@onetoone中相似,關係可雙方維護,設置了mappedBy纔可進行關係維護,關係存在第三張表
@JoinTable 與ManyToMany一起使用,可不用配(默認配置,關聯其主鍵) 注意joinColumns與inverseJoinColumns別寫反了,前者關聯當前類

name:保存關聯關係表名
joinColumns:使用@JoinColumn關聯當前類與關係表映射關係.可配置多個
@JoinColumn:name-在關係表中的列名名,foreignKey-關聯主鍵(默認主鍵)
inverseJoinColumns:使用@JoinColumn關聯另一方與關係表映射關係.可配置多個


基本使用

簡單查詢

	@Test
	@Transactional
	public void set(){
		List<School> result;
		/*#############基本查詢###############*/
		/*hibernateTemplate 與query 查詢功能很相似,此處主要以hibernateTemplate爲例*/

		/*1.通過HQL語句查詢(HQL語法與sql語法相似,有興趣詳細瞭解可上網查詢)
		 * HQL語句單表查詢可以省略SELECT T FROM ,需要注意的是HQL 不支持SELECT * FROM 
		 * 關鍵字不區分大小寫(規範書寫,sql關鍵字一律大寫),但是類名必須書寫正確(因爲要根據類名尋找相應的class,假如多個類名重名,要帶上包名)
		 * */
		
		//通過    ?   find(String queryString, Object... values) 
		
		Object[] paramValues =new Object[]{"a",1};
		String hql ="";
		/*問好出現的順序依次對應後面values(暫時沒找到怎麼根據索引對應相應的值)--這個方式以及過時*/
		hql = "FROm School s WHERE s.name=? AND s.id=? ORDER BY s.name";
		result= (List<School>) hibernateTemplate.find(hql, paramValues);
		System.out.println("template? 佔位符:"+result.get(0).getName());
		
		 /*query jpa佔位符,可以根據?索引的位置設置響應的value值*/
		hql = "FROm School s WHERE s.name=? AND s.id=? ORDER BY s.name";
		Query query = hibernateTemplate.getSessionFactory().getCurrentSession().createQuery(hql);
		query.setParameter(0, paramValues[0]);
		query.setParameter(1, paramValues[1]);
		result = query.list();
		System.out.println("query ? 佔位符:"+result.get(0).getName());
		
		//通過    :+參數別名 findByNamedParam(String queryString, String[] paramNames, Object[] values) --推薦
		String[] paramNames =new String[]{"schoolName","schoolId"};
		/*queryString中括號不是必須的只是爲了更好的區分*/
		hql = "FROM School s WHERE s.name=(:schoolName) AND s.id=(:schoolId) ORDER BY s.name";
		result= (List<School>) hibernateTemplate.findByNamedParam(hql, paramNames,paramValues);
		System.out.println("template參數 佔位符:"+result.get(0).getName());
		
		/*query*/
		query = hibernateTemplate.getSessionFactory().getCurrentSession().createQuery(hql);
		query.setParameter(paramNames[0], paramValues[0]);
		query.setParameter(paramNames[1], paramValues[1]);
		result = query.list();
		System.out.println("query參數 佔位符:"+result.get(0).getName());
		
		
		/*2.通過Example--(不支持主鍵.null.空指針,排序...*/
		School exampleEntity = new School();
		exampleEntity.setId(1);
		exampleEntity.setName("a");
		exampleEntity.setCreateTime(null);
		result = hibernateTemplate.findByExample(exampleEntity);
		System.out.println("example參數 佔位符:"+result.get(0).getName());
		
		/*3.通過queryName--hibernateTemplate.findByNamedQuery(queryName, values)
		 * 這種要求在xml中定義好:
		 * <query name="queryName"><![定義查詢語句]]></query> 
		 * 這種用法用的少忽略
		 * */
		
		/*3.通過DetachedCriteria--*/
		DetachedCriteria criteria = DetachedCriteria.forClass(School.class);
		criteria.add(Restrictions.eq("id", 1));
		criteria.add(Restrictions.eq("name", "a"));
		criteria.addOrder(Order.desc("name"));
		System.out.println("criteria 查詢:"+result.get(0).getName());
		
		/*4.通過原生sql查詢--*/
		String sql= "SELECT * FROM school s where s.id=1 AND s.name='a'";
		SQLQuery sQLQuery = hibernateTemplate.getSessionFactory().getCurrentSession().createSQLQuery(sql);
		sQLQuery.addEntity(School.class);
		sQLQuery.setResultTransformer(Criteria.ROOT_ENTITY);
		result = sQLQuery.list();
		System.out.println("sql 查詢:"+result.get(0).getName());
		
		/*#############返回的結果集###############*/
		/*1.以上結果集返回List<Object> 集合*/
		
		
		/*#############指定查詢字段,分頁,函數,連表,group by 等###############*/
		/* 1.HQL 與sql 指定查詢字段,分頁 ,連表,函數,以及group by 直接在語句中寫,很簡單,而criteria就相對比較靈活(criteria單獨講)
		 * 2.criteria分頁,使用 hibernateTemplate.findByCriteria(criteria, firstResult, maxResults)
		 * */
		
		System.out.println("success!");
	}
上述查詢返回值爲List<Object[]>,但都可以強轉爲List<School>(sQLQuery除外),其實HQL省略SELELCT 前綴是可以強轉,但一旦使用了SELECT強轉就會失敗,在query或criteria可以設置類型轉換.

query,criteria設置結果集轉換

		//原生sql
		String sql= "SELECT s.id,s.name,s.create_time as createTime FROM school s where s.id=1 AND s.name='a'";
		SQLQuery sQLQuery = hibernateTemplate.getSessionFactory().getCurrentSession().createSQLQuery(sql);
		/*轉換方式三種(暫時只考慮單表)
		 * 默認Object[]數組
		 * Transformers.aliasToBean(School.class)--實體bean
		 * Transformers.ALIAS_TO_ENTITY_MAP --map
		 * Transformers.TO_LIST --List
		 * 注意的是:map中key是列的別名, 列的別名(沒有別名就去數據本身字段名)要與bean 的property一致
		 * */
		sQLQuery.setResultTransformer(Transformers.aliasToBean(School.class));
		List<School> sqllist = sQLQuery.list();
		System.out.println("sql 查詢:"+sqllist.get(0).getName());
		
		//HQL hql與sql相同,因爲setResultTransformer是共同父類Query中的方法
		String hql = "SELECT s.id,s.name FROM School s";
		Query hqlQuery = hibernateTemplate.getSessionFactory().getCurrentSession().createQuery(hql);
		hqlQuery.setResultTransformer(Transformers.aliasToBean(School.class));
		List<School> hqllist = hqlQuery.list();
		System.out.println("hql 查詢:"+hqllist.get(0).getName());
		
		DetachedCriteria criteria = DetachedCriteria.forClass(School.class);
		criteria.setProjection(Projections.projectionList()
				.add(Projections.property("id"),"id")
				.add(Projections.property("name"),"name"))
		.setResultTransformer(Transformers.aliasToBean(School.class));
		List<School> crilist = (List<School>) hibernateTemplate.findByCriteria(criteria);
		System.out.println("criteria 查詢:"+crilist.get(0).getName());

query,criteria相關函數使用(僅以sum group by舉例)

/*函數舉例僅以sum group by舉例*/
		
		//原生sql與hql用法相似,此處僅舉hql,hql最後也是要轉換爲sql語句,hql基本都支持sql中的函數且名字都沒有怎麼換
		String hql = "SELECT SUM(s.id) as sumId,s.name FROM School s GROUP BY s.name";
		Query hqlQuery = hibernateTemplate.getSessionFactory().getCurrentSession().createQuery(hql);
		hqlQuery.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
		List<Map<String, Object>> hqllist = hqlQuery.list();
		//sum() avg(),count()等相關函數,查詢結果都爲long類型(以前低版本是integer)
		System.out.println("hql 查詢:"+hqllist.get(0).get("sumId"));
		
		//criteria
		/*小提示
		 * Projections.property 根據數據庫類型轉成相應的java類型
		 * Projections.sqlProjection 可以將根據types 將轉爲的java類型再轉換爲指定類型
		 * */
		DetachedCriteria criteria = DetachedCriteria.forClass(School.class);
		criteria.setProjection(Projections.projectionList()
				.add(Projections.sum("id"),"sumId")
				.add(Projections.groupProperty("name"),"name"))
		.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
		List<Map<String, Object>> crilist =  (List<Map<String, Object>>) hibernateTemplate.findByCriteria(criteria);
		System.out.println("criteria 查詢:"+crilist.get(0).get("sumId"));
		
		//criteria 還有另一張groupby方法
		criteria.setProjection(null);
		criteria.setProjection(Projections.sqlGroupProjection("SUM({alias}.id) as sumId,{alias}.name as schoolname", "schoolname", new String[] {"sumId","schoolname"}, new Type[] {IntegerType.INSTANCE,StringType.INSTANCE } ))
		.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
		 crilist =  (List<Map<String, Object>>) hibernateTemplate.findByCriteria(criteria);
		System.out.println("criteria 查詢:"+crilist.get(0).get("sumId"));

講了單表,再來看看連表查詢

		//原生sql
		String sql= "SELECT s.name as schoolName,st.name as studentName FROM school s,student st where s.id= st.school_id";
		SQLQuery sQLQuery = hibernateTemplate.getSessionFactory().getCurrentSession().createSQLQuery(sql);
		sQLQuery.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
		List<Map<String, Object>> sqllist = sQLQuery.list();
		System.out.println("sql 查詢:"+sqllist.get(0).get("schoolName"));
		
		//HQL除了與上述sql一樣的寫法外,還有自帶特色連表(默認是內連接)
		String hql= "SELECT st.school.name as schoolName,st.name as studentName FROM Student st";
		Query hqlQuery = hibernateTemplate.getSessionFactory().getCurrentSession().createQuery(hql);
		hqlQuery.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
		List<Map<String, Object>> hqllist = hqlQuery.list();
		System.out.println("hql 查詢:"+hqllist.get(0).get("schoolName"));
		/*	打印的sql語句   
		 *  select
		        school1_.name as col_0_0_,
		        student0_.name as col_1_0_ 
		    from
		        student student0_,
		        school school1_ 
		    where
		        student0_.school_id=school1_.id*/
		
		//criteria
		DetachedCriteria criteria = DetachedCriteria.forClass(Student.class,"s");
		criteria.createAlias("school", "st", JoinType.INNER_JOIN)
		
		.setProjection(Projections.projectionList()
				.add(Projections.property("s.name"),"studentName")
				.add(Projections.property("st.name"),"schoolName"))
		.add(Restrictions.eqProperty("s.name", "st.name"))
		.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
		List<Map<String, Object>> criterialist = (List<Map<String, Object>>) hibernateTemplate.findByCriteria(criteria);
		System.out.println("criteria 查詢:"+criterialist.get(0).get("schoolName"));
以上講了用hibernateTemplate,sql,hql,DetachedCriteria(離線查詢)查詢,其實還有另外還有在線查詢Criteria,Criteria與DetachedCriteria的主要區別,前者跟當前session綁定,而後者可供任何session使用,兩者用法相似,此處不一一進行描述.此外,JPA的entityManager的用法跟模板使用有些類似,有時間再去總結總結.
上述講了hibernate的常用,我們也會關注hibernate的一些關鍵詞,狀態,session,緩存.以上用到jpa,pa與hibernate之間的關係(hibernate與jpa)

hibernate有三種狀態(hibernate三種狀態)transient(瞬時狀態),persistent(持久化狀態)以及detached(離線狀態),但jpa有四種狀態new(新建),managed(託管),detached(遊離),removed(刪除),狀態都與hibernate有對應關係(new與removed對應transient),鑑於jpa與hibernate狀態有對應關係,現僅以hibernate狀態爲例

hibernate狀態轉換圖:


還有把transient中new 與remove區分開來


理解hibernate的三種狀態:

1.瞬時--剛new出來,或刪除

2.持久--當前與session有關聯,並且相關聯session沒有關閉,事務沒有提交

3.託管 --沒有當前session與之關聯

其中有個session的概念十分重要,session是hibernate的一個句柄對象,hibernate實體的操作基本上都是通過時session完成,每次請求都會分配一個session(session是輕量級的),但是session不是線程安全的,所以除了查詢操作外,增,添,改基本都會要求使用同一個session(使用事務,保證同一個session,spring集成hibernate session管理),每個session都有一個緩存,習慣稱之爲一級緩存,每次請求都會有自己的session從而擁有自己的一級session緩存.

session緩存減少了與數據庫交互的次數,從內存中查詢數據提高效率,session也需要保證緩存數據與數據庫的數據同步,因此,一次更改刪除都會更新session緩存(add,update,delete必須有事務,事務提交會進行檢查,不一致更新緩存數據),因此session同步的相關操作就變得比較重要,必要時,需要手動清理緩存(clear-清理所有,flush-刷新所有,refreshs(T)-刷新單個,reevict(T)-清理單個).通過setflushmode控制刷新時機


hibernate中的事務,hibernate不配置,默認使用數據庫的默認級別(mysql-REPEATABLE READ(4),oracle-READ COMMITTED(2)),稍微回顧下事務級別:

READ UNCOMMITTED:髒讀、不可重複讀、虛讀都有可能發生。

READ COMMITTED:避免髒讀,不可重複讀、虛讀都有可能發生。

REPEATABLE READ:避免髒讀、不可重複讀,虛讀有可能發生。

SERIALIZABLE:避免髒讀、不可重複讀、虛讀的發生。

更改hibernate隔離級別,通過hibernate.connection.isolation=1|2|4|8 設置.我們用同一個session保證了數據正確性,但在併發的時候,我們也要考慮數據更改的正確性,hibernate實現了悲觀與樂觀鎖

悲觀鎖實現很容易,hibernateTemplate 操作有個別備選參數,LockMode,設置lockMode=LockMode.PESSIMISTIC_WRITE即可,但是使用樂觀鎖,還需要指定相關的version(依據樂觀鎖的原理,有個判定依據verison可以是版本號,也可以是時間戳)

@OptimisticLocking(type=OptimisticLockType.ALL)-標註model中

type 可選

VERSION,檢查@javax.persistence.Version指定的屬性(默認值)

NONE,禁用任何版本檢查(即使指定了@javax.persistence.Version屬性)

ALL,檢查所有屬性

DIRTY,只檢查變化了的屬性

@Version 標註在版本號或時間戳中

使用的LockMode

		/**
		 * No lock required. If an object is requested with this lock
		 * mode, a <tt>READ</tt> lock will be obtained if it is
		 * necessary to actually read the state from the database,
		 * rather than pull it from a cache.<br>
		 * <br>
		 * This is the "default" lock mode.
		 */
		NONE( 0, "none" ),
		/**
		 * A shared lock. Objects in this lock mode were read from
		 * the database in the current transaction, rather than being
		 * pulled from a cache.
		 */
		READ( 5, "read" ),//共享鎖
		/**
		 * An upgrade lock. Objects loaded in this lock mode are
		 * materialized using an SQL <tt>select ... for update</tt>.
		 *
		 * @deprecated instead use PESSIMISTIC_WRITE
		 */
		@Deprecated
		UPGRADE( 10, "upgrade" ),//悲觀鎖
		/**
		 * Attempt to obtain an upgrade lock, using an Oracle-style
		 * <tt>select for update nowait</tt>. The semantics of
		 * this lock mode, once obtained, are the same as
		 * <tt>UPGRADE</tt>.
		 */
		UPGRADE_NOWAIT( 10, "upgrade-nowait" ),

		/**
		 * Attempt to obtain an upgrade lock, using an Oracle-style
		 * <tt>select for update skip locked</tt>. The semantics of
		 * this lock mode, once obtained, are the same as
		 * <tt>UPGRADE</tt>.
		 */
		UPGRADE_SKIPLOCKED( 10, "upgrade-skiplocked" ),

		/**
		 * A <tt>WRITE</tt> lock is obtained when an object is updated
		 * or inserted.   This lock mode is for internal use only and is
		 * not a valid mode for <tt>load()</tt> or <tt>lock()</tt> (both
		 * of which throw exceptions if WRITE is specified).
		 */
		WRITE( 10, "write" ),//等待鎖

		/**
		 * Similar to {@link #UPGRADE} except that, for versioned entities,
		 * it results in a forced version increment.
		 *
		 * @deprecated instead use PESSIMISTIC_FORCE_INCREMENT
		 */
		@Deprecated
		FORCE( 15, "force" ),

		/**
		 *  start of javax.persistence.LockModeType equivalent modes
		 */

		/**
		 * Optimistically assume that transaction will not experience contention for
		 * entities.  The entity version will be verified near the transaction end.
		 */
		OPTIMISTIC( 6, "optimistic" ),//樂觀鎖

		/**
		 * Optimistically assume that transaction will not experience contention for
		 * entities.  The entity version will be verified and incremented near the transaction end.
		 */
		OPTIMISTIC_FORCE_INCREMENT( 7, "optimistic_force_increment" ),

		/**
		 * Implemented as PESSIMISTIC_WRITE.
		 * TODO:  introduce separate support for PESSIMISTIC_READ
		 */
		PESSIMISTIC_READ( 12, "pessimistic_read" ),

		/**
		 * Transaction will obtain a database lock immediately.
		 * TODO:  add PESSIMISTIC_WRITE_NOWAIT
		 */
		PESSIMISTIC_WRITE( 13, "pessimistic_write" ),

		/**
		 * Transaction will immediately increment the entity version.
		 */
		PESSIMISTIC_FORCE_INCREMENT( 17, "pessimistic_force_increment" );
	
hibernate相關一些方法區別:

get與load: load會延遲加載,查詢不到會報異常,get找不到返回null

update與merge:update會直接更新,merge會先查詢,結果不一致纔會執行更新

save與persist:save會立即產生持久化標識符並返回,persist不保證立即產生持久化標識,無返回值

 find與iterate: 1.iterate會自動查詢二級緩存,find需手動開啓二級緩存(例如hibernateTemplate.setCacheQueries(true ))
2.iterate查詢策略(1+n,先查出id列表,返回的是代理的對象(延遲加載,與load類似),當需要加載對象時,根據對象id去緩存中查,查不到去數據庫查,最多發出1+n條查詢), find直接查(對於相同的查詢,如果開啓了二級緩存,先從緩存中取,再從數據庫中取),最多發出1條查詢語句
3.iterate使用不常用的結果集,頻繁讀寫的操作使用find

session作爲一級緩存,存儲在內存中,hibernate也支持二級緩存,hibernate二級緩存是sessionfactory級別的緩存,線程安全,所有session共享


配置二級緩存以ehcache爲例

				<!--二級緩存配置 -->
				<!-- 開啓查詢緩存 -->
				<prop key="hibernate.cache.use_query_cache">true</prop>
				<!-- 開啓二級緩存 -->
				<prop key="hibernate.cache.use_second_level_cache">true</prop>
				<!-- 高速緩存提供程序 -->
				<!-- 緩存管理器 -->
				<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory
				</prop>
ehcache.xml配置(ehcache服務器啓動會自動讀取此配置文件)

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
	<!--數據存儲位置 -->
	<diskStore path="${java.io.tmpdir}/cache" />

	<!-- Mandatory Default Cache configuration. These settings will be applied 
		to caches created programmtically using CacheManager.add(String cacheName) -->

	<!-- 關於時間的數字單位(s) 
	maxElementsInMemory 內存中最多允許保存的數據對象的數量
	maxElementsOnDisk 在磁盤上緩存的element的最大數目,默認值爲0,表示不限制。 
	external 緩存中對象是否爲永久的,如果是,超時設置將被忽略,對象從不過期;如果爲false那麼還要根據timeToIdleSeconds,timeToLiveSeconds判斷 
	timeToIdleSeconds 對象空閒時間,指對象在多長時間沒有被訪問就會失效。 默認值0,表示一直可以訪問。 
	timeToLiveSeconds 對象存活時間,指對象從創建到失效所需要的時間。默認值0,表示一直可以訪問。 
	overflowToDisk 內存不足時,是否啓用磁盤緩存 
	diskPersistent 是否在磁盤上持久化。指重啓jvm後,數據是否有效。默認爲false。
	diskExpiryThreadIntervalSeconds: 對象檢測線程運行時間間隔。標識對象狀態的線程多長時間運行一次。 
	diskSpoolBufferSizeMB: DiskStore使用的磁盤大小,默認值30MB。每個cache使用各自的DiskStore。
	memoryStoreEvictionPolicy 內存不足時數據對象的清除策略 ,默認值LRU 
		ehcache中緩存的3種清空策略: 
			FIFO(first in first out):先進先出 
			LFU(Less Frequently Used):一直以來最少被使用的。如上面所講,緩存的元素有一個hit屬性,hit值最小的將會被清出緩存。
			LRU(Least Recently Used):最近最少使用的,緩存的元素有一個時間戳,當緩存容量滿了,而又需要騰出地方來緩存新的元素的時候,那麼現有緩存元素中時間戳離當前時間最遠的元素將被清出緩存。 -->
	<!--默認cache -->
	<defaultCache maxElementsInMemory="10000"
		maxElementsOnDisk="10000000" eternal="false" timeToIdleSeconds="120"
		timeToLiveSeconds="120" diskPersistent="false" overflowToDisk="true"
		diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU">
	</defaultCache>

	<cache name="org.hibernate.cache.spi.UpdateTimestampsCache"
		maxElementsInMemory="5000" eternal="true" overflowToDisk="true" />
	<cache name="org.hibernate.cache.internal.StandardQueryCache"
		maxElementsInMemory="10000" eternal="false" timeToLiveSeconds="120"
		overflowToDisk="true" />

	<!-- java文件註解查找cache方法名的策略:如果不指定java文件註解中的region="ehcache.xml中的name的屬性值", 
		則使用name名爲com.lysoft.bean.user.User的cache(即類的全路徑名稱), 如果不存在與類名匹配的cache名稱, 則用 
		defaultCache 如果User包含set集合, 則需要另行指定其cache,例如User包含citySet集合, 則也需要添加配置到ehcache.xml中 -->
	<!-- maxElementsInMemory,name,external ,overflowToDiskb必須配置 -->

	<!-- school緩存 -->
	<cache name="schoolCache" maxElementsInMemory="10000"
		eternal="false" timeToLiveSeconds="600" overflowToDisk="false" />


</ehcache>
school

package spring.hibernate.model;

import java.util.Date;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Version;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.OptimisticLocking;
@Entity
@Table(name="school")
@OptimisticLocking
@Cache(region="schoolCache",usage=CacheConcurrencyStrategy.READ_ONLY,include="all")
public class School extends BaseEntity{
	private String des;
	private String name;
	private Date updateTime;    
	List<Student> students;
	
	public String getDes() {
		return des;
	}
	public void setDes(String des) {
		this.des = des;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	@OneToMany(fetch=FetchType.LAZY,mappedBy="school")
	public List<Student> getStudents() {
		return students;
	}
	public void setStudents(List<Student> students) {
		this.students = students;
	}
	
	@Column(name = "update_time")
	@Temporal(TemporalType.TIMESTAMP)
	@Version
	public Date getUpdateTime() {
		return updateTime;
	}
	public void setUpdateTime(Date updateTime) {
		this.updateTime = updateTime;
	}
	 
	
}
@cache 表示此對象加入二級緩存(@cache,hibernate屬於類級別緩存,spring也有方法級別的緩存--spring緩存)

rejion:對應ehcache.xml中cache的name

usage:緩存策略:NONE, READ_ONLY, NONSTRICT_READ_WRITE, TRANSACTIONAL

include默認all,還有一種non-lazy

可以看到jpa中,一些基本操作只要知道其class,就可以進行相關通用操作,我們可以使用泛型來達到提取共用方法的目的.

BaseDaoImpl

public class BaseDaoImpl<T, ID extends Serializable> implements BaseDao<T, ID> {
	/** 實體類類型 */
	private Class<T> entityClass;

	@Autowired
	protected HibernateTemplate hibernateTemplate;

	@Resource(name = "dataSource")
	protected DataSource datasource;

	public BaseDaoImpl() {
		Type type = getClass().getGenericSuperclass();
		Type[] parameterizedType = ((ParameterizedType) type).getActualTypeArguments();
		entityClass = (Class<T>) parameterizedType[0];
	}

	@Override
	public Serializable save(T entity) {
		Assert.notNull(entity);
		return hibernateTemplate.save(entity);
	}
}
SchoolDaoImpl
public class SchoolDaoImpl extends BaseDaoImpl<Label, Integer> implements LabelDao {
}
使用時直接schoolDao.save(school),不需要指定相應的class了.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章