項目中用到了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參數配置)
定義一些modelbaseEntity
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;
}
}
studentpackage 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;
}
}
schoolpackage 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;
}
}
teacherpackage 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;
}
}
idcardpackage 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>
schoolpackage 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);
}
}
SchoolDaoImplpublic class SchoolDaoImpl extends BaseDaoImpl<Label, Integer> implements LabelDao {
}
使用時直接schoolDao.save(school),不需要指定相應的class了.