项目中用到了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;
}
}
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了.