标签:style http color io os 使用 ar java for
hibernate
hibernate其实就是ormapping框架,此框架的作用就是简单话数据库的操作。
hibernate就是将用户提交的代码,参照持久化类配置文件,转换成sql语句。
1、 查询代码特别繁琐。
2、 重复性代码多,频繁的try,catch。
3、 没有对数据的缓存(就是将先将数据放入内存中,当dao层再向数据库要数据时,直接到内存中去取,速度会快很多,也避免了频繁的和数据库交互)。
4、 对sql代码的移植性很差。(不能解决sql语句跨数据库的问题)。
1、 最原始的操作数据库,所以速度比较快。
1、 因为hibernate对数据库是面向对象操作,所以不需要写sql(自动生成sql)语句,也就导致sql语句是不可控的(如:在查询数据时有很多种方式来查询到我们想要的数据,可以凭自己的项目需要来选择那种方式。但在hibernate中无法编写sql代码(自动生成),所以就导致如果这个项目对sql语句优化查询效率要求特别高的话,就不适合使用hibernate框架了).
2、 如果数据量特别大,也不适合使用hibernate。
1、 代码比较简单
2、 有对数据的缓存(hibernate对数据的缓存总共有三种:一级缓存、二级缓存、查询缓存。hibernate的一级缓存是世界级的,效率比较高。)
3、 hibernate操作数据库是以面向对象的方式操作数据库,所以sql代码的移植性比较好。
让关系型数据中的表和java中的对象产生关联,也就是以后操作数据库可以用面向对象的方式操作。
但需要一个命名为*.hbm.xml的配置文件(映射文件)进行配置,让数据库中的表和对象产生关联关系.
映射文件中需要映射的关系:
数据库中的表 java中类
表中的字段名 类中的属性名
表中字段的类型 类中属性的类型
表中关系(一对一,一对多,多对多等) java中面向对象的关系
那么通过此*.hbm.xml关系映射文件,就可以将关系型数据的表和java中的对象互相产生关联。
在hibernate中与数据库表对应的类叫持久化类此类实例化的对象叫持久化对象,统称为POJO
POJO必须是一个javaBean对象
hibernate-3.5.6-Final-dist
c3p0-0.9.1.jar 数据库连接池需要的jar包
commons-collections-3.1.jar 集合的操作工具jar包
commons-logging-1.1.1.jar 日志工具jar包
dom4j-1.6.1.jar xml的DOM解析工具
ehcache-1.5.0.jar 缓存框架
javassist-3.9.0.GA.jar hibernate的核心jar包(解决代理模式的懒加载问题)
jta-1.1.jar
log4j.jar 日志工具jar包
mysql-connector-java-5.1.10-bin.jar MySQL的驱动jar包
slf4j-api-1.5.8.jar
slf4j-log4j12.jar
antlr-2.7.6.jar
backport-util-concurrent.jar
hibernate的配置文件必须在WEB-INF/class(对应的开发目录为src)下,名称为固定写法:hibernate.cfg.xml
1、 用于配置数据库的连接信息
2、 用于描述持久化类的映射配置文件(及加载)
此配置文件打开用MyEclipse Hibernate Config Editor工具打开
选中此xml配置文件-->右键-->Open With-->Other 选择此工具双击即可
<?xml version=‘1.0‘ encoding=‘utf-8‘?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<!-- 此配置文件的根元素 -->
<hibernate-configuration>
<!-- 一个session-factory标签表示配置一个数据库 -->
<session-factory>
<!-- property标签表示一个数据项(数据库的连接信息)-->
<!-- 如果此标签的name属性为:hibernate.connection.username,就表示要设置连接数据库的用户名 -->
<property name="hibernate.connection.username">root</property>
<!-- 如果此标签的name属性为:hibernate.connection.password,就表示要设置连接数据库的密码 -->
<property name="hibernate.connection.password">123</property>
<!-- 如果此标签的name属性为:hibernate.connection.url,就表示要设置数据库的url地址(包括数据名) -->
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/数据库名</property>
<!-- hibernate.dialect:表示设置方言(在web开发中必须配置方言及设置url)
也就是告诉hibernate连接的是哪个数据库(这里是MySQL数据库)
此选项可以不用填写,因为hibernate会自动通过数据库url识别
但如果不设置,在有些环境下会出错
注意:如果要设置此值,MySQL数据库的取值范围有两个:
org.hibernate.dialect.MySQLInnoDBDialect(此值在web开发中测试时才会有有用,在java测试时会连接不到数据库)
org.hibernate.dialect.MySQLDialect(推荐使用)(在web开发和java测试是都有用)
如果发现无法自动创建表,因更换参数后再试-->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<!--
hibernate.hbm2ddl.auto:表示根据持久化类和映射文件生成表(当启动hibernate容器的时候,hibernate对表的处理情况)
取值:
validate 表示只验证表的结构是否正确,但不会创建表(默认值)
create-drop 表示当启动hibernate时创建表,当hibernate销毁时就删除表(一般不用)
create 表示每当启动hibernate时就创建此表,如果有原表,有可能会覆盖掉原表中的数据(一般不用)
update 表示当启动hibernate时就检查数据库中是否有此表,如果没有就创建表,如果有就只是验证表的结构(推荐使用)
注意:这里只是自动创建表,数据库不会自动创建。
-->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- hibernate.connection.driver_class:表示配置数据库的驱动地址 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<!-- 在控制台打印hibernate自动生成的sql语句 -->
<property name="hibernate.show_sql">true</property>
<!-- mapping标签表示:描述持久化类的映射配置文件(并加载) -->
<mapping resource="cn/domain/User.hbm.xml" />
</session-factory>
</hibernate-configuration>
在hibernate中与数据库表相对应的java类叫持久化类(持久化类必须是一个javaBean),对应的java对象叫持久化对象。
持久化类的配置文件必须和持久化类放在同一个包下,并且此配置文件的开头必须以持久化类的类名开头:持久化类名.hbm.xml(.hbm.xml为固定写法)
如有一个User的持久化类,那么它的持久化配置文件的名称应为:User.hbm.xml
必须实现序列化接口(Serializable),应防止此对象无法在网络上传输或存储在本地。
持久化类必须是一个javaBean
持久化类的属性不能使用关键字
文件的作用是让关系型数据库中的表和java中的对象产生映射关系(关联起来 )
产生此映射关系后那么通过hibernate就可以以面向对象的方式操作数据库了。
数据库中的表 java中类
表中的字段名 类中的属性名
表中字段的类型 类中属性的类型
表中关系(一对一,一对多,多对多等) java中面向对象的关系
那么通过此*.hbm.xml关系映射文件,就可以将关系型数据的表和java中的对象互相产生关联。
此配置文件用MyEclipse Hibernate Mapping Editor工具打开
选中此持久化类配置文件-->点击右键-->Open With-->Other 选择此工具双击即可
<?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: 持久化类的全名
table: 表的名称(如果表名和类名一致,就可以不写,因为其默认值和持久化类名一样)
catalog: 数据库的名称 一般不写(因为可以从数据库配置文件(hibernate.cfg.xml)的数据库url中获取到此数据库名称) -->
<class name="cn.domain.User">
<!--id 标签表示描述数据库表的主键
属性:
name: java持久化类中的主键属性的名称
column: 数据库表中主键字段的名称(可以不用写此属性,默认值和name的值一致)
type:java类中的主键的类型(完整名称) 此属性的取值还有一种:(不推荐此种写法,效率低)
如果是java.lang.Integer类型 直接写:integer(首字母小写)
java.lang.String类型 直接写:string
java.lang.Long类型 直接写:long (依次类推) 这种写法hibernate也能识别
length:表中此主键字段类型的长度(如果不写将是最大值)
-->
<id name="uid" length="10" type="java.lang.Long">
<!-- generator标签表示此主键生产器
告诉hibernate容器用什么样的方式产生此主键,这里是increment:自增长的方式
-->
<generator class="increment"></generator>
</id>
<!-- property标签用于描述一个表中普通的字段和类中的属性的关系
属性:
name:java持久化类中属性的名称,
column:数据库表中字段的名称(可以不用写此属性,默认值和name的值一致)
type:java持久化类中属性的类型(完整名称)
length:表中此字段类型的长度(如果不写将是最大值)
-->
<property name="username" length="20" type="java.lang.String"></property>
<!-- property标签用于描述一个表中普通的字段和类中的属性的关系
属性:
name:java持久化类中属性的名称,
column:数据库表中字段的名称(可以不用写此属性,默认值和name的值一致)
type:java持久化类中属性的类型(完整名称)
length:表中此字段类型的长度(如果不写将是最大值)
-->
<property name="password" length="10" type="java.lang.String" ></property>
</class>
</hibernate-mapping>
此方式,hibernate配置文件必须遵循:名称固定为:hibernate.cfg.xml,并且必须放在WEB-INF/classes(对应的开发目录为src)目录下 的规则。
用以下方式进行加载:
//创建一个hibernate配置文件加载对象
Configuration configuration = new Configuration();
//将此配置文件加载到内存中,并会根据此配置文件的配置方式,检索是否需要自动创建表(根据hibernate.cfg.xml配置文件对hibernate.hbm2ddl.auto属性的设置),并检查表的结构和java的类是否相对应
configuration.configure();
此方式,hiberate配置文件的名称及位置可以随意,但在加载配置文件时需要将此文件的路径传入。
如:hibernate的配置文件存放在cn.domain包下,并取名为abc.xml,那么应该用以下方式加载此配置文件
//创建一个hibernate配置文件加载对象
Configuration configuration = new Configuration();
//将此配置文件加载到内存中(传入配置文件的路径及名称),并会根据此配置文件的配置方式,检索是否需要自动创建表(根据hibernate.cfg.xml配置文件对hibernate.hbm2ddl.auto属性的设置),并检查表的结构和java的类是否相对应.
configuration.configure("/cn/domain/abc.xml");
SessionFactory对象中封装了一个数据库的所有信息,包括:数据库的连接信息、持久化类的映射文件信息、持久化类的信息.
SessionFactory是一个重量级的对象(比较重要的对象和特别牛的对象);
当Configuration对象加载完成hibernate配置文件和持久化类关系映射配置文件后便会通过此这些配置文件生成此SessionFactory对象,所以可以通过Configuration对象的buildSessionFactory()方法获取到此SessionFactory对象。
当hibernate配置文件加载完成时便会产生此SessionFactory对象,并会永久驻留在内存中,直到此应用在服务器上卸载。
SessionFactory对象是通过单例模式产生的,所以此对象在应用中只会有一个。
SessionFactory对象也是一个线程安全的对象,内部将共享数据的操作放在了线程同步代码块中。
Session对象就相当于JDBC中的Connection(连接)对象,在JDBC中通过Connection就可以直接用sql语句的方式对数据库进行操作。而在hibernate中可以直接通过Session对象以面向对象的方式对数据库进行操作。
Session对象可以通过SessionFactory对象的openSession()方法(表示打开一个连接) 获取到此Session对象。
和Connection(连接)对象一样 此Session对象使用完后必须调用此对象的close()方法将这个连接重新放回到数据库连接池中。
如果要对表进行增、删、改操作,必须通过调用此Session对象的beginTransaction()方法开启事务,并会返回一个Transaction事务对象,当对表操作完成后需调用此Transaction事务对象的commit()方法提交事务,如果没有开启事务,将会导致操作不成功(因为hibernate不会自动提交事务)。
一个Session对象可以开启两次以上事务,但必须提交事务后才能重新开启事务。
此方法是将一个持久化对象(参照此持久化类的映射配置文件)保存到数据库中。
注意:此方法的参数必须是一个持久化对象(也就是持久化类的示例对象)。
hibernate的配置文件及持久化类的映射配置文件应只被加载一次,并生成SessionFactory对象,所以加载这些配置文件应放在一个静态域中加载。
此工具类因以HibernateUtils命名
package cn.utils;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtils {
//此变量的访问权限为protected,表示继承了此类也可以获取到sessionFactory对象
//此对象是一个静态的表示会永久驻留在内存中,并且此对象是不可修改的
protected final static SessionFactory sessionFactory;
//hibernate的配置文件和持久化类的映射配置文件,只需要被加载一次,并生成SessionFactory对象
//把加载配置文件的方法写在一个静态域中,表示只被加载一次
static{
//获取到加载对象
Configuration configuration = new Configuration();
//加载配置文件
configuration.configure();
//获取到sessionFactory数据库对象
sessionFactory = configuration.buildSessionFactory();
}
//获取此sessionFactory对象的方法(此方法可以根据自己的需要 写与不写)
public static SessionFactory getSessionFactory(){
return HibernateUtils.sessionFactory;
}
}
cn.domain;
User
package cn.domain;
import java.io.Serializable;
//持久化类(持久化类必须是一个javaBean)
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private Integer uid;
private String username;
private String password;
public User(){}
public User(String username, String password){
this.username = username;
this.password = password;
}
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
cn.domain;
User.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: 持久化类的全名
table: 表的名称(如果表名和类名一致,就可以不写,因为其默认值和持久化类名一样)
catalog: 数据库的名称 一般不写(因为可以从数据库配置文件(hibernate.cfg.xml)的数据库url中获取到此数据库名称) -->
<class name="cn.domain.User">
<!--id 标签表示描述数据库表的主键
属性:
name: java持久化类中的主键属性的名称
column: 数据库表中主键字段的名称(可以不用写此属性,默认值和name的值一致)
type:java类中的主键的类型(完整名称) 此属性的取值还有一种:(不推荐此种写法,效率低)
如果是java.lang.Integer类型 直接写:integer(首字母小写)
java.lang.String类型 直接写:string
java.lang.Long类型 直接写:long (依次类推) 这种写法hibernate也能识别
length:表中此主键字段类型的长度(如果不写将是最大值)
-->
<id name="uid" column="uid" length="10" type="java.lang.Integer">
<!-- generator标签表示此主键生产器
告诉hibernate容器用什么样的方式产生此主键,这里是increment:自增长的方式
-->
<generator class="increment"></generator>
</id>
<!-- property标签用于描述一个表中普通的字段和类中的属性的关系
属性:
name:java持久化类中属性的名称,
column:数据库表中字段的名称(可以不用写此属性,默认值和name的值一致)
type:java持久化类中属性的类型(完整名称)
length:表中此字段类型的长度(如果不写将是最大值)
-->
<property name="username" column="username" length="20" type="java.lang.String"></property>
<!-- property标签用于描述一个表中普通的字段和类中的属性的关系
属性:
name:java持久化类中属性的名称,
column:数据库表中字段的名称(可以不用写此属性,默认值和name的值一致)
type:java持久化类中属性的类型(完整名称)
length:表中此字段类型的长度(如果不写将是最大值)
-->
<property name="password" column="password" length="10" type="java.lang.String" ></property>
</class>
</hibernate-mapping>
在根目录, WEB-INF/classes(对应的开发环境目录为src/)
hibernate.cfg.xml
<?xml version=‘1.0‘ encoding=‘utf-8‘?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<!-- 此配置文件的根元素 -->
<hibernate-configuration>
<!-- 一个session-factory标签表示配置一个数据库 -->
<session-factory>
<!-- property标签表示一个数据项-->
<!-- 如果此标签的name属性为:hibernate.connection.username,就表示要设置连接数据库的用户名 -->
<property name="hibernate.connection.username">root</property>
<!-- 如果此标签的name属性为:hibernate.connection.password,就表示要设置连接数据库的密码 -->
<property name="hibernate.connection.password">123</property>
<!-- 如果此标签的name属性为:hibernate.connection.url,就表示要设置数据库的url地址(包括数据名) -->
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/db_myhibernate_20140808</property>
<!-- hibernate.dialect:表示设置方言
也就是告诉hibernate连接的是哪个数据库(这里是MySQL数据库)
此选项可以不用填写,因为hibernate会自动通过数据库url识别
但如果不设置,在有些环境下会出错
注意:如果要设置此值,MySQL数据库的取值范围有两个:
org.hibernate.dialect.MySQLInnoDBDialect
org.hibernate.dialect.MySQLDialect(推荐使用)
如果发现无法自动创建表,因更换参数后再试-->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<!--
hibernate.hbm2ddl.auto:表示根据持久化类和映射文件生成表(当启动hibernate容器的时候,hibernate对表的处理情况)
取值:
validate 表示只验证表的结构是否正确,但不会创建表(默认值)
create-drop 表示当启动hibernate时创建表,当hibernate销毁时就删除表(一般不用)
create 表示每当启动hibernate时就创建此表,如果有原表,有可能会覆盖掉原表中的数据(一般不用)
update 表示当启动hibernate时就检查数据库中是否有此表,如果没有就创建表,如果有就只是验证表的结构(推荐使用)
注意:这里只是自动创建表,数据库不会自动创建。
-->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- hibernate.connection.driver_class:表示配置数据库的驱动地址 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<!-- 在控制台打印hibernate自动生成的sql语句 -->
<property name="hibernate.show_sql">true</property>
<!-- mapping标签表示:描述持久化类的配置文件(及加载) -->
<mapping resource="cn/domain/User.hbm.xml" />
</session-factory>
</hibernate-configuration>
cn.utils;
HibernateUtils
hibernate配置文件及持久化类映射关系配置文件 只需要加载一次 并会生成SessionFactory对象,此对象会伴随此应用一直驻留在内存中(所以在工具类中的修饰符是:final static)
如果要用sessionFactory对象,只需调用此工具类的getSessionFactory()方法或继承此工具类
package cn.utils;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
//Hibernate配置文件加载及sessionFactory对象获取 的工具类
public class HibernateUtils {
//此变量的访问权限为protected,表示继承了此类也可以获取到sessionFactory对象
//此对象是一个静态的表示会永久驻留在内存中,并且此对象是不可修改的
protected final static SessionFactory sessionFactory;
//hibernate的配置文件和持久化类的映射配置文件,只需要被加载一次,并生成SessionFactory对象
//所以把加载配置文件的方法写在一个静态域中,表示只被加载一次
static{
//获取到加载对象
Configuration configuration = new Configuration();
//加载配置文件
configuration.configure();
//获取到sessionFactory数据库对象
sessionFactory = configuration.buildSessionFactory();
}
//获取此sessionFactory对象的方法(此方法可以根据自己的需要 写与不写)
public static SessionFactory getSessionFactory(){
return HibernateUtils.sessionFactory;
}
}
cn.test;
TestUserCRUD
package cn.test;
import java.util.Iterator;
import java.util.List;
import org.hibernate.Query;
import org.hibernate.Transaction;
import org.hibernate.classic.Session;
import org.junit.Test;
import cn.domain.User;
import cn.utils.HibernateUtils;
//测试类因继承了HibernateUtils工具类所以可以直接使用sessionFactory对象
public class TestUserCRUD extends HibernateUtils{
//通过Session对象 以面向对象的方式对数据库中数据的增删改查操作
//注意:增删改查 时,如果传入一个对象操作,那么这个对象必须是一个持久化类的示例对象
@Test//增加用户
public void addUser(){
//得到连接
Session session = sessionFactory.openSession();
//事务开启事务,并得到事务对象
Transaction transaction = session.beginTransaction();
//创建一个用户
User newUser = new User("王五","789");
//将此用户保存到表中
session.save(newUser);
//提交事务
transaction.commit();
//关闭连接
session.close();
}
@Test//修改用户
public void updateUser(){
//得到连接
Session session = sessionFactory.openSession();
//事务开启事务,并得到事务对象
Transaction transaction = session.beginTransaction();
//方式一:(推荐使用)
//通过主键,先查询到要修改的用户对象
User updateUser = (User)session.get(User.class, 1);
//修改此对象要修改的值(注意 主键不能修改)
updateUser.setPassword("abc");
//将此用户传入update方法,修改此用户(注意,是通过此用户的主键进行修改的)
session.update(updateUser);
/*
//方式二:(不推荐使用)
//创建一个用户(注意这里必须将此用户的属性值填满,不然没填满的字段,修改时将给表中的此字符赋值为null)
User updateUser1 = new User("aaa","abc");
//并比此用户的主键设置为数据库中要修改的用户的主键
updateUser1.setUid(1);
session.update(updateUser1);
*/
//提交事务
transaction.commit();
//关闭连接
session.close();
}
@Test//删除用户
public void deleteUser(){
//得到连接
Session session = sessionFactory.openSession();
//事务开启事务,并得到事务对象
Transaction transaction = session.beginTransaction();
//方式一:
//通过主键来得到要删除的用户
User deleteUser = (User)session.get(User.class, 3);
//将此对象传入delete方法中(这里的删除是通过此对象的id进行删除的)进行删除
session.delete(deleteUser);
//方式二:
//创建一个用户对象(此对象中除了主键以外的其他属性可以不填)
User deleteUser1 = new User();
//设置此用户的主键(必须设置主键,因为hibernate就是通过主键来删除用户的)
deleteUser.setUid(2);
//将此用户传入delete方法进行删除(通过此对象的主键进行删除的)
session.delete(deleteUser1);
//提交事务
transaction.commit();
//关闭连接
session.close();
}
@Test//查询用户
public void findUser(){
//得到连接(因为是查询,所以不需要开启事务)
Session session = sessionFactory.openSession();
//方式一:查询一个用户(通过主键)
User user = (User)session.get(User.class, 1);
System.out.println("uid:"+user.getUid()+",username:"+user.getUsername()+",password"+user.getPassword());
//方式二:批量查询(通过hql语句),这里表示查询所有的User,并得到一个Query对象
Query q = session.createQuery("from User");
//通过Query对象的list方法将查询的所有User对象 以一个List集合返回
List<User> userList = q.list();
//遍历这个list集合
Iterator<User> it = userList.iterator();
while(it.hasNext()){
User u = it.next();
System.out.println("uid:"+u.getUid()+",username:"+u.getUsername()+",password"+u.getPassword());
}
//关闭连接
session.close();
}
@Test//唯一的主键
public void uniquePrimaryKey(){
//得到连接
Session session = sessionFactory.openSession();
//事务开启事务,并得到事务对象
Transaction transaction = session.beginTransaction();
//得到主键为4的用户
User user = (User)session.get(User.class, 4);
//创一个新的用户
User user1 = new User();
//并设置其主键也为4
user1.setUid(4);
//当再进行修改是会报错,因为经过hibernate处理的同一个持久化类的实例对象出现了两个对象主键同时为4
session.update(user1);
/*
*那么hibernate是怎么检测到的有两个对象的主键都为4呢 (以下结果为猜的,也可以参考:持久化对象的状态);
* 当用session的get()方法查询这个用户时,hibernate就记录了这个对象的引用地址(堆中的地址),及主键 为4,
* 并以此主键为key 对象引用地址为value的形式存储在一个Map集合中(此Map专用于存储User持久化类的实例对象的 主键及对象的引用地址)
*
* 当用session.update()方法进行修改时,hibernate发现此对象的主键为4,便到此持久化对象对应的Map集合中去取出主键为4的对象的引用地址,
* 但发现和此对象的引用地址不一致(也就证明不是同一个对象),也间接的说明了有一个以上的对象引用了一个主键(主键不唯一了),那么就会抛出此异常
* org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [cn.domain.User#4]
* 所以经过hibernate处理的同一个持久化类的所有实例对象,的主键必须唯一
*/
//提交事务
transaction.commit();
//关闭连接
session.close();
}
}
以上都是通过加载hibernate配置文件时会在数据库中自动创建表(如果在hibernate.cfg.xml配置文件中配置了的话),但在实际开发中都是先创建数据库中的表,所以MyEclipse工具提供了一种根据表来自动生成:持久化类和映射配置文件及hibernate的配置文件,前提是此工程必须是一个hibernate工程,并且要创建好对应的包。
1. 第一步:
2. 第二步:
进入MyEclipse Hibernate perspective界面后 点击树形菜单栏-->右键 New
3. 第三步:
那么数据库就导入好了
选中当前工程-->点击MyEclipse--> Project Facets--> Install Hibernate Facet然后会弹出提示框
1. 第一步:
2. 第二步:
3. 第三步:
4. 第四步:
那么此工程就是一个hibernate工程了,并且已经建好了hibernate.cfg.xml配置文件
1. 第一步:
2. 第二步:
3. 第三步:
4. 第四步:
那么持久化类和持久化类映射配置文件就自动生成好了
主键生成器在持久化类映射配置文件的 id标签中用<generator class="主键生成器"></generator>标签进行设置
主键生成器只有在添加记录时才会被启动
这种方式,hibernate会自动从当前数据库中查询出最大的主键,然后再在此主键上加1,设置成下一条记录的主键
此种方式是在数据库中查询出最大的主键然后加1。然后hiberante会将此主键保存在一个静态的全局变量中,下一次还需要向此表中增加数据时,只需要从此静态变量中获取,并将此静态变量的值加1即可,从而就不需要每次都到数据库中进行获取最大的主键了。
所以此中方式的主键必须是数字,并且效率较低,因为多了一条查询语句。
这种方式是:数据库中自动对此表的主键进行生成
但此表必须支持主键自动生成的功能(MySQL支持,但Oracle中的表不支持自动生成主键),并且还设置了此功能(就是MySQL中的主键自增长)
使用此种主键生成器,当调用save(保存操作)方法时,一定会被执行成功,不管是否使用evict或clear方法。详情请参照以下 save方法
此种方式效率高
此方式是程序员自己编写方法生成主键,然后手动将此主键赋值给持久化对象,进行操作
此种方式,hibernate会自动生成UUID字符串的方式作为此持久化对象的主键
此种方式主键必须为字符串类型
此方式是根据Oracle数据库中序列来生成主键
在hibernate中将一个持久化对象分行三个状态,临时状态、持久化状态、脱管状态,来对对象进行处理。
其实还有一个保存状态,通过save方法保存的对象的状态就是保存状态。如果此对象没有主键,hibernate会根据主键生成机制,生成此对象的主键,并将此保存到session的一级缓存中,并将此对象的状态改为保存状态,当在执行事务提交时hibernate会检查session对象的缓存中的所有对象,如果为保存状态就执行insert语句。
在hibernate中一个持久化对象的状态是针对某一个Session对象而言的,比如说,现在有两个Session对象,当一个持久化对象通过第一个Session对象进行了save()操作,那么这个对象在第一个Session对象中的状态就是持久化状态,而此对象在第二个Session对象中的状态为临时状态。
也可以这么说:只要一个持久化对象被存储在了Session对象中并且还没有执行,那么此对象的状态就是: 持久化状态或删除状态或脱管状态。只要某一个持久化对象没有被保存到Session对象中,那么此对象的状态就是:临时状态
持久化对象的状态转换图
当一个持久化对象手动被创建出来时,那么它的状态就为临时状态(其实就是一个新建的持久化对象还没有经过hibernate内存时)。
Session对象没有保存此持久化对象,那么此对象的状态就是临时状态;
就是当一个持久化对象经过了hibernate(增删改查时),那么此对象的状态就是持久化状态,如果是通过hibernate查询出来的对象,那么此对象的状态也是持久化状态。
当一个对象变为持久化状态时,hibernate的快照机制,会创建出此对象的快照(相当于保存了此对象的所有信息(包括此对象的引用地址)),
当在提交事务时,hibernate会检查所有的持久化状态的对象,将此持久化状态的对象和其开始创建的快照进行对比,如果此对象发生了修改,那么hibernate会自动生成update语句,自动更新表中的对应的此条记录,而不用手动进行update操作;
也间接性的说明了对象的持久化状态,只有在开启手动提交事务的情况下才会有此状态(也就是说只有开起了手动提交事务,那么更新了持久化状态的对象,才会在提交事务时自动更新表中对应的此条记录);
一个持久化类的任意对象,不允许有两个或以上ID一致的持久化对象,的状态变成持久化状态(也就是说,同一个持久化类的持久化对象,不允许有两个或以上主键一致的持久化对象,的状态变为持久化状态);
持久化状态的对象都会被保存到Session对象中(Session对象引用了持久化状态对象的存储地址)。
当一个持久化对象执行了Session对象的delete()方法,那么这个持久化对象在Session对象中就变成了删除状态了。
此持久化对象在Session对象中变为删除状态前,Session对象会检索此对象是否有主键,如果没有主键,将会抛出异常。
删除状态的对象也都会被保存到Session对象中(Session对象引用了持久化状态对象的存储地址)。
如果一个对象在session对象中的状态是删除状态,那么再用session对象的get方法或load方法获取此对象,不管是否提交事务,那么将获取不到此session对象(通过主键来对比的)。
代码示例:
@Test
public void testDelete(){
Session session = sessionFactory.getCurrentSession();
Transaction transaction = session.beginTransaction();
//获取到这个学生。
Student stu = (Student)session.get(Student.class, 1);
//将session对象中的一级缓存中的所有对象清除掉
session.clear();
//将此学生加入到session的缓存中,并将其状态该为删除状态。
session.delete(stu);
//再从数据库中查找此对象(这里将查找不到此stu对象,返回值为null)
Student stu1 = (Student)session.get(Student.class, 1);
/*这也说明了当去查找一个对象时,先会到此session对象中的一级缓存中去查找(通过此主键),
如果这个对象在session对象的一级缓存中存在,并且是删除状态,就直接返回null,而不会去数据库中查询此对象了
*/
transaction.commit();
}
当事务提交后,那么此对象就属于脱管状态了,那么再修改此对象的值,就不会被更新到对应表中的对应记录上面去了,
当调用Session对象的evict(Object)方法,将这个对象在Session中的状态改为脱管状态。当调用Session对象的clear()方法时,是将此Session中的所有持久化对象的状态都改为脱管状态。
并且Session对象的close方法也会将这个Session对象中的所有持久化对象的状态改为脱管状态,
当一个持久化对象,的状态变为了持久化状态,那么同时,hibernate也会生成此对象的快照(将相当于克隆了一个此对象,并保存了此被克隆对象的引用地址),当执行提交事务时,hibernate就检查所有的持久化状态的对象,并将将此对象和此对象的快照进行对比,如果是主键对比不上,将抛异常(因为主键不能被修改),如果其他字段对比不上,就说明了此对象被修改过,那么hibernate就是自动生成update语句,将更新此对象对应表中对应的记录(根据主键查找的此记录),如果此对象和此对象对应的快照一致,就不会发出update语句;
package cn.test;
import org.hibernate.Transaction;
import org.hibernate.classic.Session;
import org.junit.Test;
import cn.domain.User;
import cn.utils.HibernateUtils;
public class UserTest extends HibernateUtils{
@Test//持久化对象的状态测试
public void statusTest(){
//打开连接
Session session = sessionFactory.openSession();
//开始手动提交事务
Transaction transaction = session.beginTransaction();
//根据主键查询出此User对象,因为是查询出来的对象(经过了hibernate),那么此对象的状态就是持久化状态
//并在hibernate内部生成了此对象的快照
User user = (User)session.get(User.class, 1);
//修改此对象的password属性的值为“111111”
user.setPassword("111111");
//当提交事务时,hibernate会检查状态为持久化状态的所有对象,并将这个对象和其对应的快照进行对比
//这里把user对象和其快照进行对比,那么肯定不一致,hibernate就会自动生成update语句,将此对象传入进行更新
//提交事务
transaction.commit();
//关闭连接
session.close();
}
}
/*
* 结果:User表中主键为1的记录,的password字段的值被修改为了“111111”
*/
Session对象在hibernate中代表一个连接,比如JDBC中的Commection对象,但Session对象中封装了Commection连接对象,并且还增加了一些hibernate特有的功能。
Session对象是通过SessionFactory对象的openSession方法得到的。
此方法接收的是一个持久化对象,调用此方法时但会检查此对象的主键,如果映射配置文件中 主键的生成器是identity(数据库自己生成主键),那么此方法会直接生成insert sql语句,并将此sql语句保存到事务对象的sql语句缓存区中。然后在数据库中查询出下一个主键的值,设置到此持久化对象中(以保证此对象在事务提交时不会自动生成inser语句了),再将此对象保存到Session对象中,并生成快照。
@Test//
public void test1(){
//生成ID的方式为identity
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
User user = new User("aaaaa","bbbbb");
//保存此用户时就已经生成了insert sql语句保存在了transaction事务对象的sql缓存区
session.save(user);//
//此处虽然在session对象中移除了所有的持久化对象,包括user,但在transaction事务对象中的insert sql语句没有删除
session.clear();
//所以在执行事务提交时还是会保存user对象
transaction.commit();
session.close();
}
如果是其他ID生成器,那么save方法会将删除此对象的主键(如果有主键的话),然后再将此对象保存到Session对象中(对象的状态变为持久化状态)。
此方法接收一个持久化对象,当调用此方法时,此方法会检查传入的持久化对象,如果没有主键,就抛出异常。
如果此持久化对象有主键,那么就将此持久化对象保存到session对象中(对象的状态变为持久化状态),并生成一个快照对象,注意这里的快照并不是此持久化对象的快照,这样就保证了快照一定和此对象不一致,执行commit方法时也就一定会通过此对象来生成update sql语句(详情请参考以下 事务对象的事务提交操作)
执行clear方法就是将此session对象中的所有持久化对象移除(也就是将session对象中的所有持久化对象的状态变为临时状态),提交commit方法时,由于commit方法无法从session对象中获取到持久化对象,所以也就无法对持久化对象进行操作。
@Test//测试clear方法
public void testClear(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//创建一个新的User对象
User user = new User("1111","2222");
//将此对象添加到Session对象中,并会清除此对象的主键(如果有主键的话)
session.save(user);
//移除Session对象中的所有持久化对象
session.clear();
//因为clear将session对象中的持久化对象移除了,commit方法就从session对象中获取不到持久化对象了。也就不会user对象进行操作了
transaction.commit();
/*
* 结果是user对象无法被添加到表中(不会抛异常)
*/
session.close();
}
此方法接收的是一个session对象中存在的持久化对象,当调用evict方法时将会从session对象中移除此对象,让移除此对象后commit就无法从session对象中获取到此持久化对象,也就无法对此对象进行操作了
@Test//测试evict方法
public void testEvict(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//创建两个对象
User user1 = new User("111111","111111111");
User user2 = new User("2222222","22222222");
//将这两个对象添加到session对象中
session.save(user1);
session.save(user2);
//执行evict方法,只将user1从session对象中移除
session.evict(user1);
//当执行commit方法时,commit方法从session中获取的所有持久化对象中没有user1对象,所以user1不会被添加到表中
//详情参考:事务对象的事务提交操作
transaction.commit();
session.close();
/*
* 结果:
* user1不会被保存
* user2对象会被保存成功
*/
}
此方法接收两个参数,第一个是持久化类的字节码对象,第二个参数是主键
此方法相当于用主键查询出表中的某一条记录。
此方法接收一个hql表达式,可以查询出表中的多条记录,并会转换成持久化对象。并返回一个Query结果集对象。可以调用此Query对象的list方法获取到查询出来的多个对象;
此方法接收两个参数,第一个参数是持久化类的字节码对象,第二个参数是主键。
此方法相当于用主键查询出表中的某条记录.
但此方法查询出来的对象是懒加载模式。
此方法接收一个持久化对象, 此方法将会到数据库中重新查询出传入的持久化对象,并覆盖掉原有的持久化对象。以达到从数据库更新内存中的持久化对象的目的。
此方法是将一级缓存中的持久化对象以相应的sql语句更新到数据库中(此方法不会提交事务,也不会清除一级缓存中的持久化对象,只是将相应的sql语句提交给了数据库)。
当调用session.flush()方法时所做的事情。
1. 获取session对象中的所有持久化对象。
2. 检查所有持久化对象的状态,如果为保存状态(通过save方法加入的)或删除状态(通过delete方法加入的),就直接根据其快照 产生insert或delete sql语句,提交给数据库。并将这些对象的状态改为持久化状态。
3. 检查所有的持久化对象的状态,如果为持久化状态,那么就和其快照进行对比,如果和其快照不一致,就直接根据这个对象生成update sql语句,提交给数据库。(并不会提交事务)
4. 在检查以上2、3步的同时会检查此持久化对象的关联对象(就是有关系的对象(一对多,多对多等)),是否有级联操作,如果有就级联操作此关联的对象。还会检查是否对此关联对象执行关系操作,如果有就维护此对象与此关联对象的关系。
当一个持久化对象被添加到Session对象中时,那么这个持久化对象的状态就变成了持久化状态。所以Session对象决定了持久化对象的状态。
在hibernate中执行增删改操作必须开启事务,不然无法提交成功。
在hibernate中的事务对象只归属于某一个Session对象(只能和一个Session对象绑定)。
每一个Transaction事务对象中都有一个sql缓存区,这个缓冲区只缓存增、删、改操作的sql语句。当提交事务时Transaction事务提交对象会将此缓存中的所有sql语句遍历出来执行。
错误纠正:其实有可能没有这个sql缓存区,而是直接将此sql语句提交给了数据库了,只是没有提交事务而已。
也有可能有sql缓存区,用的是JDBC的批处理的sql缓存区。(目前还不确定)
当调用了Transaction事务对象的commit()方法(事务提交)时,hibernate不会马上提交事务,而是执行以下操作后再提交事务。
此事务对象会检索此与之对应的Session对象中的所有持久化状态的对象,检索此对象主要检索两个方面。
第一个方面:
检索此对象的主键是否有值,如果没有值,就自动生成此对象的insert sql语句(增加操作),并把此sql语句保存到Transaction事务对象的sql语句缓存区,这里就不会检索此对的第二个方面了.
如果此对象的主键有值,就不管,直接检索此对象的第二个方面。
第二个方面:(只有当此对象的主键有值时才会检索此对象的此方面)
检索此对象和此对象的快照是否一致,如果如果不一致,那么就根据此对象的主键自动生成update sql语句(修改操作),并将此语句保存到Transaction事务对象中的sql语句缓存区。
如果一致就不做任何操作
将Session对象中的所有删除状态的对象,对照此类的映射配置文件,将此持久化对象对应的表和此对象的主键遍历出来。并生成delete sql语句(删除操作),并将此语句保存到Transaction事务对象的sql语句缓存区。
执行了以上操作后,hibernate才会真正的提交事务。hibernate会将此事务对象中的sql语句缓存区中的所有sql语句遍历出来并执行。
事务提交并不会移除session对象中的持久化对象,但会更新此对象的快照对象。
@Test//commit方法对session对象中的持久化对象的快照进行更新的测试
public void test2(){
//得到一个session连接对象
Session session = sessionFactory.openSession();
//开启手动提交事务(第一次的事务),事务对象为:transaction
Transaction transaction = session.beginTransaction();
//获取一个持久化对象
User user = (User)session.get(User.class, 1);
user.setUsername("abc");
//执行commit事务提交,会在表中更新此user对象的数据,因为user和其快照不一致了
transaction.commit();
//开启另一个事务
Transaction transaction1 = session.beginTransaction();
//此处不会生成sql语句,也就是不会有什么操作,也就是说明了,user对象在上次提交事务时的快照进行了更新,
//所以此处的commit将user对象和其快照对比时是一致的,所以就不会发生update操作
transaction1.commit();
session.close();
}
一个session对象可以多次开启事务,但必须提交事务后才能再次开启。每提交一次事务,那么都会检查session对象中的持久化对象是否和其快照一致,如果不一致也会自动执行update方法
@Test//session对象的多次提交事务测试
public void testCommit(){
//得到一个session连接对象
Session session = sessionFactory.openSession();
//开启手动提交事务(第一次的事务),事务对象为:transaction
Transaction transaction = session.beginTransaction();
//获取一个持久化对象
User user = (User)session.get(User.class, 1);
//执行commit事务提交,这里什么也不会发生,因为user对象和其快照是一致的
transaction.commit();
//开启另一个事务
Transaction transaction1 = session.beginTransaction();
//更新user的username属性
user.setUsername("abc");
//此处会对user进行更新,也间接性的说明了commit方法不会移除session中的持久化对象
//这里也说明了只要提交一次事务,那么都会对session中的所有持久化对象进行和其快照进行对比,如果不一致也会执行update方法
transaction1.commit();
session.close();
}
直接调用transaction. rollback()方法即可。注意只能回滚还没有提交的事务,提交了的事务部能被回滚。
当用Session对象进行查询时,查询出来的对象的状态就是持久化状态,而且查询是没有事务的,当执行一个查询操作时hibernate会马上生成sql语句并马上从数据库中查询出此数据,而不会等到事务提交以后再查询。
持久化对象的状态是相对于某一个Session对象而言的。也就是说 此对象在一个Session中是某一个状态,而在另一个Session对象中可能又是另一种状态。
@Test//持久化对象的状态范围示例
public void testSession(){
Session session1 = sessionFactory.openSession();
Transaction transaction1 = session1.beginTransaction();
//获取一个user对象,那么这个持久化对象保存在了在session1这个对象中,并生成了此对象的快照
//也就是说,user对象在session对象中的状态是持久化状态
User user = (User)session1.get(User.class, 1);
//修改此对象的用户名
user.setUsername("aa");
//transaction1事务对象提交,会修改user对象在表中对应的记录信息
transaction1.commit();
//关闭此连接
session1.close();
Session session2 = sessionFactory.openSession();
Transaction transaction2 = session2.beginTransaction();
//再次修改user中的用户名
user.setUsername("bbbbbbbbbbb");
//应为transaction2事务提交对象是session2对象中获取的,那么此事务对象只会检查session2中的持久化对象并操作
//但这里transaction2事务对象没有获取到user这个持久化对象,所以这里不会在表中更新此user对象中的数据
transaction2.commit();
session2.close();
}
session的产生方式有两种,第一种是通过sessionFactory.openSession()此方式来获得session对象,另一种方式是通过sessionFactory.getCurrentSession()方法获取到此session对象。
此方式是通过sessionFactory对象的openSession();方法获取到此session对象的。
此中方式获取到的session对象的生命周期是:当调用sessionFactory.openSession对象时产生,当调用session.close()方法时销毁。
通过此方式获取到的session对象都是一个新的session连接对象,也相当于是一个独立的session连接对象,但有时候无法满足业务需求,比如以下业务需求图:
所以在以上图的业务需求中不适合用单独的session来做此业务,所以hibernate就提供了第二种方式获取session对象。
此方式是通过sessionFactory对象的getCurrentSession();方法获取到此session对象的。
此中方式获取到的session的生命周期,当创建线程时创建此session对象,当此线程结束时session对象销毁。
此种方式获取到的session对象是当前线程范围内的session对象。当一个用户访问时(开启一个独立的线程),hibernate会创建一个用于存储session对象的ThreadLocal对象(线程范围内的共享变量)。当用getCurrentSession();方法获取此session对象时,此方法会去当前线程中的ThreadLocal对象中去取此session对象,如果返回为null,就新创建一个session对象,存入ThreadLocal对象中,并返回此session对象。如果不为null就直接返回此session对象。
也就是说getCurrentSession()方法获取到的session对象是当前线程中唯一的session对象。
在使用此对象时必须开启事务(包括增删改查),不然会抛异常,并且在使用完成后只需要用transaction事务对象提交事务即可,而不需要关闭session对象,session对象在提交事务时会自动关闭(hibernate自动完成的)。所以可以这样理解,此session对象和事务绑定在一起了。
如果要用此对象,必须在hibernate.cfg.xml配置文件中配置上<property name="current_session_context_class">thread</property>,表示创建线程范围内的session对象
//线程范围内的seesion对象测试
//必须在hibernate.cfg.xml配置文件中配置<property name="current_session_context_class">thread</property>
//表示创建线程范围内的session对象
public class SessionTest extends HibernateUtils{
//调用此方法,创建一个班级和一个学生,必须在同一个事务下完成。
@Test
public void SessionTest(){
Session session = sessionFactory.getCurrentSession();
Transaction transaction = session.beginTransaction();
try{
//创建一个班级
createClasses();
//创建一个学生
createStudent();
//int i = 1/0;如果在此处发生异常,以上班级和学生将会创建不成功。
//在此处打断点(只有在执行完以下事务提交时,createClasses();createStudent(); 方法中的事务才会被提交)
transaction.commit();
/*
* 这样就保证了在提交事务前出了异常,就绝对不可能创建出班级和学生;
*/
}catch(Exception e){
//回滚事务
transaction.rollback();
System.out.println("创建班级和创建学生不成功!!");
}
}
//创建一个班级
public void createClasses(){
//获取线程范围内的session对象
Session session = sessionFactory.getCurrentSession();
//因为在SessionTest方法中已经开启了事务,此处开不开事务都一样
Classes cla1 = new Classes("13java1班");
session.save(cla1);
//但此处不能提交事务
}
//创建一个学生
public void createStudent(){
//获取线程范围内的session对象
Session session = sessionFactory.getCurrentSession();
Student stu1 = new Student("一一",20);
session.save(stu1);
//但此处不能提交事务
}
}
hiberante中的关系就是怎么将java中的面向对象转换成数据库中的一对一、一对多、多对多 关系 。
比如一个班级有多个学生,当我们查询这个班级时,没用获取此班级的学生,或没有使用此班级的学生,那么Hibernate不会去查询此班级的所有学生,只有当使用此班级中的学生时,Hibernate才会到数据库中查询出此班级的所有学生。所以在优化时也因注意以下的操作;(此方式是用代理方式实现的)
注意事项:
比如查询一个班级,如果在session对象关闭前没有调用此班级中的所有学生,那么当session对象关闭后(session.close()),如果在调用此班级的所有学生会抛异常。
如:
@Test
public void fun(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//查询出一个课程对象
Course cou2 = (Course)session.get(Course.class, 2);
//cou2.getStudents().size();//如果没有此操作以下会抛异常,
//这里就相当于在数据库中查询出了此课程对象的所有学生
transaction.commit();
session.close();
//获取此课程对象的所有学生的个数。(如果没有以上:cou2.getStudents().size();语句 操作此处会抛异常,因为hibernate去查询此课程对象的所有学生时发现连接已被关闭)
Integer len = cou2.getStudents().size();
System.out.println("len=:"+len);
}
关系优化重点
一般多对多关系在操作时,应用少的一方操作多的一方法。而在一对多的双向关联中就没有此问题,但有另一个问题,在少的一方操作,hibernate每次都会产生一个更新语句来更新多的一方的外键,所以在一对多操作时应以多的一方操作一的一方。
多对多的例子:
比如一个课程对象要添加一些学生(这些学生在数据库中以存在,并且已有其他的课程)。应该用这个课程去添加这些学生,而不是用这些学生去添加这个课程。
用这个课程去添加这些学生的特点:(少的一方去添加多的一方,推荐使用)
用这个课程时去添加所有的学生。
第一步:查询出此课程对象;
第二步:获取到这个课程的所有学生的集合;
第三步:将这些学生查询出来;
第四步:将这些学生添加到此课程的所有学生的集合中;
以上并没有用到这些学生的所有课程的集合,所以数据库就不会查询出所有被添加学生的所有课程对象;
只是查询出了这个课程的所有学生。(sql只会产生一句)(并且代码要少)
用这些学生去添加这个课程的特点:(多的一方去添加少的一方:不推荐使用)
用这些学生去添加此课程
第一步:查询出此课程对象;
第二步:查询出这些学生
第三步:获取到这些学生的所有课程的集合。
第四步:将此课程添加到这些学生的这些集合中。
以上第三步会大大降低效率,因为会查询出这些学生每一个学生的所有课程对象。(会增加很多sql语句,效率会降低)(并且代码要多)
一对多的双向关联的例子:
比如一个班级要添加多个被新创建的学生
用这些学生去添加这个班级(多的一方去 添加 一的一方,推荐使用)
第一步:创建出这些学生
第二步:查询出这个班级
第三步:将这个班级添加到这些学生的班级引用属性中。
在生成sql语句时,因为多的一方的映射配置文件没有inverse属性(强制维护关系)所以,hibernate就不会去检查学生的映射配置文件了,直接保存学生时维护关系(设置此学生的外键) ,所以此中方式比以下方式少了一步,所以效率要高(虽然代码会多一些)
用这个班级去添加这些学生(一的一方 去添加 多的一方,不推荐使用)
第一步:创建出这些学生
第二步:查询出这个班级
第三步:将这些学生添加到这个班级的set集合中
而此种方式,hibernate在生成sql语句时检查班级对象,发现多了几个学生,就先在数据库中保存这些学生(没有设置外键),再查看班级映射配置文件中集合标签的inverse属性的值是否为:"false",如果为false,才更新这些学生的外键。
所以此处多了最后一步更新学生的外键操作,所以不推荐使用此种方式,
总结:从以上例子中可以看出,只要是一对多的双向关系操作(单向操作随意),就以多的一方操作效率更高。只要是多对多的关系操作,那么就以少(少就是数量少)的一方操作效率更高.
在一个持久化对象中用什么集合来存储另一个持久化对象,那么就在持久化映射配置文件中添加一个什么样的标签来描述此集合。
如:
在一个持久化类中用Set集合来引用了另一个持久化类,那么就在持久化映射配置文件中添加一个set标签来描述此集合,hiberante会将此集合转换为表中的关系。
如:
public class Classes implements Serializable {
private Integer cid;//主键
private String name;//班级名称
private Set<Student> students;//代表这个班级的学生
...以下是get和set方法;
}
操作还分为很多中,比如将一个学生从此Set集合中移除,那么只会解除此学生和此班级之间的关系(就是学生的外键被清空了),
将一个数据库中已有的学生添加到此Set集合中,那么这个学生的外键会被修改为当前的这个班级的主键。
如果要删除班级或学生只能通过session对象的delete方法进行删除
单项关联就是在java对象中 一个对象可以关联到另一个对象,但另个对象却关联不到现在的这个对象。
如在班级类中添加一个set集合,用于存储此班级所有的学生。
那么此班级这个持久化类的映射配置文件中就需要添加一个set标签来描述此关系了
如:(如果是其他的集合就用相应的标签来描述即可)
<set name="students" cascade="all" inverse="false"><!-- 描述此set集合标签 -->
<key><!-- 外键 -->
<!-- 描述描述引用了外键的字段,此字段会被添加到学生student表中 -->
<column name="cid"></column>
</key>
<!-- 描述哪个表需要引用外键 -->
<one-to-many class="cn.domain.Student"/>
</set>
<!-- 以上可以看着:Student表中的cid外键字段引用了Classes表中的主键(cid)字段 -->
set标签
此set标签就是描述此班级中的set集合。
属性
name: 就是描述此班级中用于存储学生的set集合的属性名。
cascade: 此属性是级联的意思,当对班级表进行某一种操作时,如果涉及到了学生表中的数据,是否执行级联操作。
此属性的取值范围:
save-update: 当对班级进行保存和更新时,就就对此学生也执行保存和更新操作(如果需要保存的话,就是此学生和快照不一致的话)
delete: 当对班级进行删除时,同时删除此班级中的所有学生
all: 就是以上两种取值都包含。(推荐选此值)
inverse: 此属性的意思是 是否不维护关系。(缺省值为false)(这里的关系就是学生表的外键的引用,这里是是否维护此外键)
取值范围:
true: 就是不维护班级和学生之间的关系(比如在添加一个班级时,只会添加此班级和此班级中的所有学生,而不会给此学生的外键添加此班级主键的引用)
false: 意思是维护班级和学生之间的关系(默认值,推荐选此值);
key标签
此key标签是描述学生表的外键的。
column标签
描述学生表外键的列
属性
name:就是给学生表中的外键取一个名字
one-to-many标签(一对多)
描述哪一个表需要此外键
属性
class: 就是需要外键的表对应的持久化类的完整类名
当配置好此配置文件后,如果hibernate的配置文件中设置了自动创建表,那么hibernate会根据此配置文件来创建出一个学生表的cid外键字段引用了班级表的主键字段 这样的两张表
//一对多的单项关联的练习
public class RelationTest2 extends HibernateUtils{
//3、新建一个班级的时候同时新建一个学生,但不建立关系
@Test
public void addStudentClass(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//第一种方式:
//Classes net1 = new Classes("13.net1班");
//Student stu1 = new Student("一一",21);
//
//session.save(net1);
//session.save(stu1);
//第二种方式:将Classes.hbm.xml关系映射配置文件中的set标签中的inverse属性的值改为true
//表示不更新关系。
Classes net2 = new Classes("13.net2班");
Student stu2 = new Student("二二",22);
Set<Student> set = new HashSet<Student>();
set.add(stu2);
net2.setStudents(set);//Classes.hbm.xml配置文件中set表的cascade属性的值必须是save-update或all
session.save(net2);
transaction.commit();
session.close();
}
//4、已经存在一个班级,新建一个学生,建立学生与班级之间的关系
//需要将Classes.hbm.xml关系映射配置文件中的set标签中的inverse属性的值改为false
//表示更新关系
@Test
public void addStudentRelation(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
Classes net1 = (Classes)session.get(Classes.class, 3);
Student stu3 = new Student("三三",23);
//这里会在stu3的外键上引用net1的主键
net1.getStudents().add(stu3);//Classes.hbm.xml配置文件中set表的cascade属性的值必须是save-update或all
//这里会自动更新net1的数据,因为net1的状态是持久化状态
transaction.commit();
session.close();
}
// 5、已经存在一个学生,新建一个班级,把学生加入到该班级
@Test
public void addClassesStudentRelation(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
Student stu5 = (Student)session.get(Student.class, 5);
//新建立一个班级
Classes java3 = new Classes("13java3班");
Set<Student> set = new HashSet<Student>();
//将这个查询出来的学生添加到此班级中
set.add(stu5);
java3.setStudents(set);
//将此班级添加到Session对象中
session.save(java3);
transaction.commit();
session.close();
}
// 6、把一个学生从一个班级转移到另一个班级
@Test
public void updateStudentClass(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//获取到要添加的班级
Classes java1 = (Classes)session.get(Classes.class, 1);
//获取到要转移的学生
Student stu2 = (Student)session.get(Student.class, 2);
//将此学生添加至此班级即可
java1.getStudents().add(stu2);
transaction.commit();
session.close();
}
// 7、解除一个班级和一个学生之间的关系,
//这里只是移除关系(就是将学生的外键删除),不会删除学生
@Test
public void removeClassesStudentRelation(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//获取到这个班级
Classes net1 = (Classes)session.get(Classes.class, 3);
//获取到这个学生
Student stu6 = (Student)session.get(Student.class, 6);
//将这个学生从这个班级中删除(注意这里只会将stu6学生中的外键删除,不会删除stu6这个学生)
net1.getStudents().remove(stu6);
//因为net1是这个对象的状态是持久化状态,所以这里不需要执行update语句
transaction.commit();
session.close();
}
//8、解除一个班级和一些学生之间的关系
//解除主键为4的班级与 主键为4、5、6的学生的关系
@Test
public void removeClassesStudentRelation2(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//得到主键为4的班级
Classes cla4 = (Classes)session.get(Classes.class, 4);
//得到此班级中的所有学生
Set<Student> students = cla4.getStudents();
//遍历这些学生
Iterator<Student> it = students.iterator();
while(it.hasNext()){
Student stu = it.next();
if(stu.getSid() >= 4 && stu.getSid() <= 6){
//如果符合条件就删除
it.remove();
}
}
//应为cla4对象的状态是持久化状态,所以这里不需要写update语句
transaction.commit();
session.close();
}
// 9、解除该班级和所有的学生之间的关系
//注意这里只是解除关系(将学生的外键清空),不是删除学生
@Test
public void removeClassesAllStudentRelation(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//得到这个班级
Classes cla4 = (Classes)session.get(Classes.class, 4);
//将这个班级中的学生清空
cla4.setStudents(null);
transaction.commit();
session.close();
}
//11、已经存在一个班级,已经存在多个学生,建立多个学生与班级之间的关系
@Test
public void classesAddStudent(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//查询出此班级
Classes cla4 = (Classes)session.get(Classes.class, 4);
//查询出这些学生
Student stu3 = (Student)session.get(Student.class, 3);
Student stu4 = (Student)session.get(Student.class, 4);
Student stu5 = (Student)session.get(Student.class, 5);
Student stu6 = (Student)session.get(Student.class, 6);
//将这些学生添加到此班级中
Set<Student> students = cla4.getStudents();
students.add(stu3);
students.add(stu4);
students.add(stu5);
students.add(stu6);
transaction.commit();
session.close();
}
//12、删除学生(这里是将此学生从此数据库中删除)
@Test
public void removeStudent(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//第一中方式
////查询出此学生
//Student stu6 = (Student)session.get(Student.class, 6);
////将此学生从此数据库中删除
//session.delete(stu6);
//第二种方式
//新建一个学生
Student stu6 = new Student();
//设置其主键为要删除学生的主键
stu6.setSid(6);
//直接删除此学生(将此学生添加到session对象中,并将其状态改为删除状态)
session.delete(stu6);
transaction.commit();
session.close();
}
/*13、删除班级(从数据库中删除,不是移除关系)
* 1删除班级的时候同时删除学生(需要将Classes.hbm.xml配置文件中set标签的cascade属性的值改为all或delete)
*/
@Test
public void removeClassesStudent(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//得到此班级
Classes cla4 = (Classes)session.get(Classes.class, 4);
//删除此班级(同时会将此班级中的所有学生删除)
session.delete(cla4);
transaction.commit();
session.close();
}
/*13、删除班级(从数据库中删除,不是移除关系)
* 2在删除班级之前,解除班级和学生之间的关系(只删除班级,不删除学生)
*/
@Test
public void removeClasses(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//获取此班级
Classes cla3 = (Classes)session.get(Classes.class, 3);
//先解除此班级和所有学生之间的关系
cla3.setStudents(null);
//然后在删除此班级
session.delete(cla3);
transaction.commit();
session.close();
}
}
一对多的双向关联是在一对多的单向关联的基础上建立起来的,所以必须要先操作完成单向关联;
需要在“多”的一方的持久化类中添加对“一”的一方的引用属性,比如这里的学生类中需要建立一个对班级类的属性引用属性;
一对多的关系,在多的一方维护关系,效率比较高(因为不许再次修改多的一方的外键了);
如:
public class Student implements Serializable {
private Integer sid;//主键
private String name;//学生姓名
private Integer age;//学生年龄
private Classes classes;//引用班级
...以下是get和set方法;
}
在配置文件中描述此班级的属性;
<!-- 描述 此班级中的班级属性 -->
<!-- may-to-one表示描述 多对一
属性
name:为学生表中引用班级的属性名
class:表示被引用的类(班级)的完整名称
column:描述学生表中的外键字段
cascade:级联,当对学生表进行某一种操作时,如果涉及到了班级表,是否执行级联操作。
取值范围:
save-update:表示当对学生表进行操作保存或更新时,也对班级进行保存或更新(推荐使用)
delete:表示只当删除学生时如果涉及到了班级,那么将班级也一起删除
(不推荐使用,当删除学生时会将此班级也删除,并且还会删除此班级中的所有学生)
all:以上两种都包括。
-->
<many-to-one name="classes" class="cn.domain.Classes" column="cid" cascade="save-update"></many-to-one>
//一对多的双向关联
public class RelationTest extends HibernateUtils{
//反向操作
//反向操作就是在多的一方操作,这样就减少了sql代码(不需要每次都更新学生表的外键,来维护关系)
//通过增加学生来增加班级
@Test
public void addStudentClasses(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//创建一个班级
Classes net3 = new Classes("13.net3班");
//创建一个学生
Student stu7 = new Student("七七",22);
//将此班级添加到此学生中
stu7.setClasses(net3);
//保存该学生,这里保存学生会将班级一并保存,并且建立关系
session.save(stu7);
transaction.commit();
session.close();
}
//新建一个学生,将此学生添加至一个已有的班级中,(反向操作)
@Test//从此测试方法,生成的sql语句来看,反向操作的sql语句要少,所以效率要高
public void addStudent(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//创建出一个学生
Student stu8 = new Student("八八",22);
//查询出这个班级
Classes cla1 = (Classes)session.get(Classes.class, 1);
//将此班级添加到此学生中
stu8.setClasses(cla1);
//保存此学生
session.save(stu8);
transaction.commit();
session.close();
}
//将某一个学生从某一个班级中移除(反向操作)
@Test
public void removeStudent2(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//获取到此学生
Student stu8 = (Student)session.get(Student.class, 8);
//删除此学生中的班级
stu8.setClasses(null);
transaction.commit();
session.close();
}
//删除学生,如果学生表的映射配置文件中的many-to-one标签的 cascade属性的值是delete或all就会将此学生的班级也删除。
@Test
public void removeStudent(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//查询出一个学生
Student stu7 = (Student)session.get(Student.class, 6);
//将其删除
session.delete(stu7);
transaction.commit();
session.close();
}
}
多对多的关系在数据库中是靠第三张表来维护的,所以操作多对多的关系都是在操作第三张表;
如:
解除关系
把第三张表的一行数据删除掉
建立关系
在第三张表中增加一条记录即可
变更关系
在第三张表中,先解除现在的对象的关系,再创建与另一个对象关系
多对多关系用MyEclipse通过表来创建java类(表的反向工程),自动生成的java类不是多对多的关系,而是一个 一对多和一个 多对一 java类组合起来的。
多对多关系,以少的一方操作效率比较高(比如现在 一个课程对象要添加一些学生,应从这个课程对象中去添加这些学生,而不是从这些学生对象中去添加这个课程)在以上的《注意事项重点》 中已讲过;
学生持久化类:
//学生持久化类
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private Integer sid;
private String name;
private Integer age;
private Set<Course> courses;//存储所有的课程
public Student(){}
public Student(String name, Integer age){
this.name = name;
this.age = age;
}
以下是get和set方法...
}
课程持久化类:
//课程持久化类
public class Course implements Serializable {
private static final long serialVersionUID = 1L;
private Integer cid;
private String name;
private Set<Student> students;//存储所有的学生
public Course(){}
public Course(String name){
this.name = name;
}
以下是get和set方法...
}
学生持久化类的映射配置文件:
<?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="cn.domain.Student">
<id name="sid" length="6" type="java.lang.Integer">
<generator class="increment"></generator>
</id>
<property name="name" length="15" type="java.lang.String"></property>
<property name="age" length="2" type="java.lang.Integer"></property>
<!--
以下set是描述的第三张表:student_course(关系表)
name:描述的是当前Student(学生)持久化类中引用Course(课程)的set集合的属性名
cascade:级联,当对Student学生操作时,如果涉及到了Course(课程)时是否执行级联
取值:save-update:表示只当进行保存或更新时级联操作(这里推荐使用save-update,如果用以下两种,当删除学生时会将课程一起删除)
delete:表示只当进行删除时级联操作
all:表示以上两种都包含
table:第三张关系表的名称
inverse:Student类是否不维护关系
false为需要维护(缺省的,推荐的)
true为不需要维护
key:是描述的当前持久化类(Student)在student_course表中的外键
column:当前持久化类(Student)在student_course表中的外键的名称
name:名称
many-to-many:描述的是多对多的关系
class:描述的是student_course表中要关联的另一个类(课程持久化类)
column:描述的是student_course表中要关联的另一个类的外键名称
-->
<set name="courses" cascade="save-update" table="student_course" inverse="false">
<key>
<column name="sid"></column>
</key>
<many-to-many class="cn.domain.Course" column="cid"></many-to-many>
</set>
</class>
</hibernate-mapping>
课程持久化类的映射配置文件:
<?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="cn.domain.Course">
<id name="cid" length="6" type="java.lang.Integer">
<generator class="increment"></generator>
</id>
<property name="name" length="15" type="java.lang.String" />
<!--
set标签描述的是第三张表的关系
name:描述的是此Course课程类中引用的Student学生类的set集合的属性名
cascade:级联,当对Course课程表操作时,如果涉及到了Student(学生)时是否执行级联
取值:save-update:表示只当进行保存或更新时级联操作(这里推荐使用save-update,如果用以下两种,当删除课程时会将此课程的学生一起删除,如果用此中方式,必须先解除关系后再删除)
delete:表示只当进行删除时级联操作
all:表示以上两种都包含
table:第三张表(关系表)的名称
key标签描述的是当前Course课程表在第三张表(关系表student_course)中的外键
column标签:当前Course课程表在第三张表(关系表student_course)中的字段名称
属性:name:名称
many-to-many:描述的是多对多的关系
属性:class:描述的是student_course表中要关联的另一个类(学生持久化类)
column:描述的是student_course表中要关联的另一个类的外键名称
-->
<set name="students" cascade="save-update" table="student_course">
<key>
<column name="cid"></column>
</key>
<many-to-many class="cn.domain.Student" column="sid"></many-to-many>
</set>
</class>
</hibernate-mapping>
package cn.test;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.hibernate.Transaction;
import org.hibernate.classic.Session;
import org.junit.Test;
import cn.domain.Course;
import cn.domain.Student;
import cn.utils.HibernateUtils;
//多对多的练习《学生对课程》
public class RelationTest extends HibernateUtils{
//多对多操作时,以少的一方操作效率会高一些(sql语句少些)
//新建课程的同时新建学生 并创建关系
@Test
public void createStudentCourseRelation(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
Course course1 = new Course("javaWeb");
Student stu1 = new Student("一一",20);
Student stu2 = new Student("二二",21);
//第一种方式:(建议使用)
//创建一个存储学生的set集合
Set<Student> students = new HashSet<Student>();
//将学生一和学生二都存储到此集合中
students.add(stu1);
students.add(stu2);
//将此集合设置到此课程中(建立关系)
course1.setStudents(students);
//将此课程保存到数据库中(会级联保存学生,
//因为:课程类(Course)的映射配置文件,中的set标签的cascade属性的值是"save-update",所以在保存课程时会级联保存学生)
session.save(course1);
//第二种方式:(不推荐)
//创建一个存储课程的set集合
//Set<Course> courses = new HashSet<Course>();
//将此课程添加到此集合中
//courses.add(course1);
//
//将此集合设置到 学生1和学生2的课程集合中(建立关系)
//stu1.setCourses(courses);
//stu2.setCourses(courses);
////保存学生1和学生2(会级联保存课程
//因为:学生类(Student)的映射配置文件,中的set标签的cascade属性的值是“save-update”,所以在保存学生时会级联保存课程 )
//session.save(stu1);
//session.save(stu2);
transaction.commit();
session.close();
}
//已经存在一个课程,新建一个学生,建立课程和学生之间的关系
@Test
public void addStudentRelation(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//获取到这个课程
Course cou1 = (Course)session.get(Course.class, 1);
//创建一个学生
Student stu3 = new Student("三三", 22);
//将这个学生保存到课程中的students集合中
cou1.getStudents().add(stu3);
//此处的事务提交会自动保存cou1课程(因为cou1是一个持久化状态的对象)
transaction.commit();
session.close();
}
//5、已经存在一个学生,新建一个课程,建立课程和学生之间的关系
@Test
public void addCourseRelation(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//获取到这个学生
Student stu3 = (Student)session.get(Student.class, 3);
//创建一个课程
Course cou2 = new Course("Oracle");
//将这个课程添加到学生的课程集合中
stu3.getCourses().add(cou2);
//此处的事务提交会自动保存cou1课程(因为cou1是一个持久化状态的对象)
transaction.commit();
session.close();
}
//把已经存在的一些学生加入到已经存在的一个课程中
@Test//将sid为4567的学生添加到cid为2的课程中
public void addStudentCourseRelation(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//获取到这个课程
Course cou2 = (Course)session.get(Course.class, 2);
//获取到这些学生
List<Student> students = session.createQuery("from Student where sid in(4,5,6,7)").list();
//第一种方式(推荐使用)
//将这些学生添加到此课程中的学生集合中
//这里是从少的一方进行操作,这里只会查询出此班级的所有学生,而不会查询出这些学生的所有班级(因为没有用到),所以会大大提升效率(sql语句会大大减少)
cou2.getStudents().addAll(students);
//第二种方式:不推荐使用
//因为在调用getCoures()方法时,hibernate会将数据库中此 学生 的所有课程查询出来,
//以下有4个学生,所以就需要查询这4个学生每一个学生的所有课程,会大大降低效率,所以对多对关系不适合从多的一方操作少的一方
//students.get(0).getCourses().add(cou2);
//students.get(1).getCourses().add(cou2);
//students.get(2).getCourses().add(cou2);
//students.get(3).getCourses().add(cou2);
//
//
//session.save(students.get(0));
//session.save(students.get(1));
//session.save(students.get(2));
//session.save(students.get(3));
//此处会自动进行更新,因为cou2是一个持久化状态的对象
transaction.commit();
session.close();
}
//把一个学生加入到一些课程中
@Test//将id为7的学生添加到id为3、4的课程中
public void addStudentCourse(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//获取到此学生
Student stu7 = (Student)session.get(Student.class, 7);
//获取到id为3、4的课程
List<Course> Courses34 = session.createQuery("from Course where cid in(3,4)").list();
//第一种方式:会产生5条sql语句(在少的一方操作。推荐使用)
//将3、4的课程添加到学生的课程集合中
stu7.getCourses().addAll(Courses34);
//第二种方式:会产生6条sql语句(在多的一方操作。不推荐)
//将此学生添加到3和4课程对象的学生集合中。
//for(Course course : Courses34){
//course.getStudents().add(stu7);
//}
transaction.commit();
session.close();
}
//把多个学生加入到多个课程中
@Test
public void addStudentCourseMany(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//获取到多个学生
List<Student> students = session.createQuery("from Student where sid in(1,2,3)").list();
//获取到多个课程
List<Course> courses = session.createQuery("from Course where cid in(1,2)").list();
//方式一:把这些学生添加到这些课程中(推荐),在少的一方操作,课程的数量要少,所以推荐使用此种方式
for(Course cou : courses){
cou.getStudents().addAll(students);
}
//方式二:把这些课程添加到每个学生中(不推荐)
for(Student stu : students){
stu.getCourses().addAll(courses);
}
transaction.commit();
session.close();
}
//把一个已经存在的学生从一个已经的课程中移除
@Test//其实就是在第三张表中移除此关系
public void removeStudentCourseRelation(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//移除id为3和课程为3的关系
//获得此课程
Course cou3 = (Course)session.get(Course.class, 3);
//获取的学生
Student stu3 = (Student)session.get(Student.class, 3);
//将此课程中此学生的课程集合中删除
stu3.getCourses().remove(cou3);
transaction.commit();
session.close();
}
//把一些学生从一个已经存在的课程中移除
@Test
public void removeStudentCourseRelation2(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//得到此课程
Course cou1 = (Course)session.get(Course.class, 1);
//遍历此课程中的学生
Set<Student> students = cou1.getStudents();
Iterator<Student> it = students.iterator();
while(it.hasNext()){
Student stu = it.next();
//如果此学生的id等于1或2就将其从此集合中删除(删除关系)
if(stu.getSid() == 1 || stu.getSid() == 2){
it.remove();
}
}
transaction.commit();
session.close();
}
// 把一个学生从一个课程转向到另外一个课程
@Test//将id为1的学生 从id为2的课中移到id为1的课程中去
public void test(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//获取此学生原有的课程
Course cou2 = (Course)session.get(Course.class, 2);
//获取到此学生要移转的课程
Course cou1 = (Course)session.get(Course.class, 1);
//获取到此学生
Student stu1 = (Student)session.get(Student.class, 1);
//方式一:自动生成了6条sql语句(从少的一方操作。推荐使用)
//得到此学生的所有课程
Set<Course> courses = stu1.getCourses();
//将此id为2的课程从此集合中删除
courses.remove(cou2);
//将id为1的课程添加到此集合中
courses.add(cou1);
//方式二:自动生成了7条sql语句(从多的一方操作。不推荐使用)
//cou2.getStudents().remove(stu1);
//cou1.getStudents().add(stu1);
transaction.commit();
session.close();
}
//只删除课程
@Test
public void removeCourse(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//得到要删除的课程
Course cou4 = (Course)session.get(Course.class, 4);
//移除此课程中的所有学生(移除此课程和此课程中所有学生的关系)
cou4.setStudents(null);
//再删除课程
session.delete(cou4);
transaction.commit();
session.close();
}
//只删除学生
@Test
public void removeStudent(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//得到要删除的学生
Student stu3 = (Student)session.get(Student.class, 3);
//移除此学生的所有课程(移除此学生的课程与此学生的关系)
stu3.setCourses(null);
//再删除此学生
session.delete(stu3);
transaction.commit();
session.close();
}
//删除课程并将此课程中的学生一起删除(不建议这样做)
@Test
public void removeCourseStudent(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//得到此课程
Course cou3 = (Course)session.get(Course.class, 3);
//得到此课程的所有学生
Set<Student> students = cou3.getStudents();
//遍历这些学生
Iterator<Student> it = students.iterator();
while(it.hasNext()){
Student stu = it.next();
//将这些学生中的所有课程的关系移除
stu.setCourses(null);
//再删除此学生
session.delete(stu);
}
//再删除此课程
session.delete(cou3);
transaction.commit();
session.close();
}
}
在数据库中操作一对一的关系:比如现在又两张表,Person(人),Address(地址)。 将Person的主键字段建立一个外键引用Address表的主键,就相当于一个人对应一个地址。
人的持久化类
public class Person implements Serializable {
private Integer pid;//主键
private String name;//姓名
private Integer age;//年龄
private Address address;//地址
public Person(){}
public Person(String name, Integer age){
this.name = name;
this.age = age;
}
以下是get和set方法...
}
地址的持久化类
public class Address implements Serializable {
private Integer aid;
private String site;//地址
public Address(){}
public Address(String site){
this.site = site;
}
以下是get和set方法...
}
人的映射配置文件
<?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="cn.domain.Person">
<id name="pid" length="10" type="java.lang.Integer">
<generator class="foreign">
<param name="property">address</param>
</generator>
</id>
<property name="name" length="10" type="java.lang.String"></property>
<property name="age" length="2" type="java.lang.Integer"></property>
<!--
one-to-one标签:表示一对一关系
name属性:描述引用另一个类的属性名
class属性:描述引用另一个类的完整类名
constrained:描述此表的主键是否创建外键约束引用Address的主键(建立外键约束)
cascade:当对此Person操作时,如果涉及到了Address类是否级联操作。
-->
<one-to-one name="address" class="cn.domain.Address" constrained="true" cascade="all"></one-to-one>
</class>
</hibernate-mapping>
地址的映射配置文件
<?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="cn.domain.Address">
<id name="aid" length="11" type="java.lang.Integer">
<generator class="increment"></generator>
</id>
<property name="site" length="40" type="java.lang.String"></property>
</class>
</hibernate-mapping>
package cn.test;
import org.hibernate.Transaction;
import org.hibernate.classic.Session;
import org.junit.Test;
import cn.domain.Address;
import cn.domain.Person;
import cn.utils.HibernateUtils;
//一对一的单向关联
public class RelationTest extends HibernateUtils{
@Test//创建有关系的表
public void createRelationTable(){
}
@Test//添加一个人及一个地址
public void addPersonAddress(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
Person person = new Person("二二",23);
Address address = new Address("四川达州广安");
person.setAddress(address);
session.save(person);
transaction.commit();
session.close();
}
@Test//删除一个人和这个地址,cascade属性的值必须为:"all"或“save-update”
public void test1(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//查询出此人
Person person1 = (Person)session.get(Person.class, 1);
//删除此人
session.delete(person1);
transaction.commit();
session.close();
}
}
在hibernate优化中发出的sql语句越少效率越高(还需参照数据的大小,比如一种方式:虽然发出的sql语句少但查询出的数据很多很多,而另一种方式,虽然多发出了一些sql语句但查询出来的数据相对而言要少的多,此时就应重新选择使用的方式了)。
懒加载,hibernate通过代理模式从写了此这些对象中的方法,只有当使用这个属性时才会到数据库中去查询出此属性的值,这样就大大的提升了程序的效率了;
所以如果要在session对象关闭后使用被懒加载的数据,必须要在session对象还没关闭前调用获取一次此数据,以便从数据库中查询出此数据。
类的懒加载就是对此对象属性的懒加载(不包括关系的属性(比如引用了另一个类的集合或引用了另一个类的属性))。
就是获取一个对象时当只有获取此对象的属性时,hibernate才会从数据库中查询出此条记录。(除主键外)。
类的懒加载需要用session对象的load方法,查询出此对象,那么此对象才会被懒加载。
代码示例:
//类的懒加载
@Test
public void classLazy(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//获取某一个班级
//调用load方法用主键获取到Classes表的字段
//load方法是类的懒加载,也就是当你用到了cla1对象中的属性时才会到数据库中去查询
Classes cla1 = (Classes)session.load(Classes.class, 1);
//执行到此处不会产生sql语句(也就没有与数据库交互)
//此处也不会产生sql语句,因为只获取此对象的主键,hibernate会去数据库中查询,而是直接返回load方法传入的主键;
System.out.println(cla1.getCid());
//此处才会到数据库中查询出此班级的所有信息,但不包括所有的学生
System.out.println(cla1.getName());
transaction.commit();
session.close();
}
集合的懒加载,在关系中就是”一”的一方对”多”的一方进行懒加载,所以要在”一”的一方的set(或其他集合的标签)标签中设置一个lazy属性,此属性有三个取值:
false:表示此集合不需要被懒加载,立即加载。
说明:当查询出”一”的一方的对象时,就马上加载此对象的集合(“多”的一方)。
true:表示此集合需要被懒加载(缺省值。推荐使用)
说明:当使用到此”一”的一方的对象中集合中的元素时(“多”的一方),就马上加载此集合中的所有元素(这里的元素可以代表学生)。
extra:更强度的懒加载(一般不推荐使用)
说明:此值表示更强度的懒加载,当获取此集合的长度时不会在数据库中查询此集合中的所有元素,而是用聚合函数在数据库查询出的此集合的个数
代码示1:(”一”的一方的映射配置文件中的set标签的lazy属性的值为true)
//集合的懒加载(也就是对多的一方的懒加载)
//需要在一的一方的持久化类映射配置文件中的set(此标签是对应集合的)标签的lazy属性的值必须要为true(缺省值也是true)
@Test
public void setLazy(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//获取此班级对象,此处只会查询出此班级的信息(因为这里没有使用load方法(类的懒加载),所以这里一开始就是查询出此班级对象的所有属性的值,但不包括学生的集合)
Classes cla1 = (Classes)session.get(Classes.class, 1);
//获取此班级的所有学生,这里不会产生任何sql语句,
Set<Student> students = cla1.getStudents();
//执行到此处hibernate才会到数据库中去查询此班级的所有学生
int len = students.size();
System.out.println(len);
transaction.commit();
session.close();
}
代码示例2:(”一”的一方的映射配置文件中的set标签的lazy属性的值为extra)
//集合的懒加载(也就是对多的一方的懒加载)
//需要在一的一方的持久化类映射配置文件中的set(此标签是对应集合的)标签的lazy属性的值必须要为extra(更懒形式的加载)
@Test//更强度的懒加载
public void setLazyExtra(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//获取此班级对象,此处只会查询出此班级的信息(因为这里没有使用load方法(类的懒加载),所以这里一开始就是查询出此班级对象的所有属性的值,但不包括学生的集合)
Classes cla1 = (Classes)session.get(Classes.class, 1);
//获取此班级的所有学生,这里不会产生任何sql语句,
Set<Student> students = cla1.getStudents();
//获取此集合的长度
int len = students.size();//对应的sql语句为:select count(sid) from Student where cid =?//这个sql语句可以看出并没有查询出此集合中的元素
System.out.println(len);
transaction.commit();
session.close();
}
此中方式其实用不着懒加载,因为对”一”的一方进行懒加载,但”一”的一方的对象,只有一个。所以懒不懒加载对效率的影响不大,(使用器缺省值即可,缺省值是需要被懒加载);
在”多”的一方的隐式配置文件中的many-to-one标签中设置lazy属性的值为:no-proxy(缺省值,推荐使用此值),表示对”一”的一方使用懒加载(值也可以是false,表示不对”一”的一方使用懒加载)。
配置文件示例:(为Student类(”多”的一方)的映射配置文件)
<many-to-one name="classes" class="cn.domain.Classes" column="cid" cascade="save-update" lazy="no-proxy"></many-to-one>
代码示例:
//集合的懒加载(对"一"的一方 的懒加载)
//需要在"多"的一方的持久化类映射配置文件中的many-to-one(多对一)标签的lazy属性的值必须要为no-proxy(懒加载,缺省值,推荐使用)
@Test//更强度的懒加载
public void setLazy1(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//获取到某一个学生对象(因为此处不是用load方法,所以或查询出此学生的所有属性,但不包括老师(关系属性))
Student stu1 = (Student)session.get(Student.class, 1);
//获取到该学生的老师(此处不会产生任何的sql语句)
Classes cla = stu1.getClasses();
//此处才会到数据库中去查询出此学生的老师
System.out.println(cla.getName());
transaction.commit();
session.close();
}
抓取策略就是根据某一种方式生成查询的sql语句。主要针对查询“多“的一方。
抓取策略的值需要在集合(这里是set)标签中设置fetch属性的值:
比标签的取值范围:
join: 左外连接
表示以左外连接的方式生成查询sql语句(注意,此种方式,比如在查询一个班级时,没有使用这个班级的学生,也会以左外连接查询出此班级的所有学生,所以有时候效率会降低)。
(还应当注意,如果需求的数据可以形成子查询sql语句,那么join(左外连接)将失效)
select: 缺省值
表示生成一条一条的查询sql语句进行查询(缺省值)。
subselect: 子查询
表示生成子查询的查询sql语句进行查询(推荐使用)。(还应当注意,如果需求的数据可以形成子查询sql语句,那么join(左外连接)将失效)
<?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="cn.domain.Classes">
<id name="cid" length="11" type="java.lang.Integer">
<generator class="increment"></generator>
</id>
<property name="name" length="10" type="java.lang.String"></property>
<!-- set标签就相当于描述set集合, -->
<!--
fetch属性 表示生成什么样的查询sql语句
join:表示用左外连接的方式生成sql语句
select:表示直接一条一条的select查询语句生成sql语句。(缺省值)
subselect:表示以子查询的方式生成sql语句(推荐)
-->
<set name="students" cascade="all" inverse="false" lazy="true" fetch="subselect">
<key><!-- 外键 -->
<!-- 描述描述引用了外键的字段,此字段会被添加到学生student表中 -->
<column name="cid"></column>
</key>
<!-- 描述哪个表需要引用外键 -->
<one-to-many class="cn.domain.Student"/>
</set>
<!-- 以上可以看着:Student表中的cid外键字段引用了Classes表中的主键(cid)字段 -->
</class>
</hibernate-mapping>
//左外连接测试,set标签的fetch的值为join(获取一个班级的所有学生)
@Test//
public void test(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//获取到此班级
Classes cla4 = (Classes)session.get(Classes.class, 1);
//获取到此班级的所有学生
Set<Student> students = cla4.getStudents();
/*当fetch属性的值为join时,以上查询此班级语句生成的sql语句
select
classes0_.cid as cid0_1_,
classes0_.name as name0_1_,
students1_.cid as cid0_3_,
students1_.sid as sid3_,
students1_.sid as sid1_0_,
students1_.name as name1_0_,
students1_.age as age1_0_,
students1_.cid as cid1_0_
from
Classes classes0_
left outer join
Student students1_
on classes0_.cid=students1_.cid
where
classes0_.cid=?
这里还没有用到此班级的学生,而此处已经将此班级的所有学生查询出来了。也就是说用左外连接有时会降低效率。
*/
//遍历这些学生
for(Student stu : students){
System.out.println(stu.getName());
}
System.out.println(cla4.getName());
/*
* 以上操作 Classes持久化类的映射配置文件中的set标签中的fetch属性的值为:select(缺省值)的话,以上会产生两条sql语句。
*
* 如果以上操作Classes持久化类的映射配置文件中的set标签中的fetch属性的值为:join的话,那么只会产生一条sql语句
*
*/
transaction.commit();
session.close();
}
//子查询的测试,set标签的fetch的值为subselect(查询所有班级的所有学生)(推荐使用)
//需将 Classes持久化类的映射配置文件中的set标签中的fetch属性的值设置为subselect
@Test
public void test2(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//获取到所有的班级
List<Classes> classess = session.createQuery("from Classes").list();
for(Classes cla:classess){
Set<Student> students = cla.getStudents();
for(Student stu : students){
System.out.println(stu.getName());
}
}
/*
如果以上fetch的值为:select(缺省值),将会产生n+1条sql语句(这里的n就是少的一方的数量(这里代表班级的数量))。
如果以上fetch的值为:subselect(子查询),只会产生2条sql语句。(并且会以子查询的方式生成sql语句)
*/
transaction.commit();
session.close();
}
hiberante中的一级缓存是在session对象中,一级缓存的生命周期也就是session对象的生命周期。
hibernate中的一级缓存缓存的是私有的数据(只能当前线程能够访问)。
而session时保存在当前线程中的session对象,其他线程不能访问,所以也就保证了数据的安全性。
此session对象的缓存生命周期,是根据此session对象的生命周期而定的(也就是说,每个session对象中都有一个一级缓存而且和其他session对象的一级缓存是独立开的,并且必须通过此session对象才能向此session对象中的一级缓存中存入值)。
一个对象被保存在了session对象中(一个对象的状态是持久化状态),那么这个对象就被保存在了这个session对象的一级缓存中。
就是通过session对象的save、update、get、load、createQuery方法将此对象放入到session对象中的一级缓存中。虽然delete方法也就是将一个对象放入到了一级缓存中,但此对象的状态就为删除状态了,也就无法获取到此对象了。
当调用session对象的get或load方法时,此方法会先到此session对象的一级缓存中去查找此对象,如果在缓存中找到了此持久化对象,并且此对象的持久化状态不是删除状态,那么直接返回此持久化对象,如果此对象是删除状态,就返回null。如果在此session对象的一级缓存中没有找到此持久化对象(通过主键),那么才会到数据库中进行查询出持久化对象。
注意createQuery方法不会到缓存中去查找对象,而是直接到数据库中查找对象。
调用session对象的evict(Object obj)方法时,会移除掉session对象中一级缓存中的此对象(调用evict方法传入的持久化对象)。
当调用session对象的clear方法时,会删除此session对象的缓存中的所有持久化对象。
当调用session对象的refresh(Object obj1)方法时,refresh方法会从数据库中从新查询出此对象,并将一级缓存中的此对象覆盖掉,从而实现了刷新一级缓存中的持久化对象。
代码示例:
//refresh 是重新将一个缓存中的对象从数据库中查询出来,并覆盖掉缓存中的这个对象
@Test
public void testRefresh(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//从学生表中查询出主键为1 的学生(此学生的姓名叫"一一")
Student stu1 = (Student)session.get(Student.class, 1);
//设置此学生名称叫“张三”
stu1.setName("张三");
//此方法是刷新一级缓存中的此对象
//此方法会通过此对象的主键,从数据库中重新查询出此对象,并将一级缓存中的此对象覆盖掉。
session.refresh(stu1);
//此处会打印"一一"
System.out.println(stu1.getName());
transaction.commit();
session.close();
}
当调用session. flush()方法时,会将session对象一级缓存中的所有持久化对象更新到数据库中.
当调用session.flush()方法时所做的事情。
1. 获取session对象中的所有持久化对象。
2. 检查所有持久化对象的状态,如果为保存状态(通过save方法加入的)或删除状态(通过delete方法加入的),就直接根据其快照 产生insert或delete sql语句,提交给数据库。并将这些对象的状态改为持久化状态。
3. 检查所有的持久化对象的状态,如果为持久化状态,那么就和其快照进行对比,如果和其快照不一致,就直接根据这个对象生成update sql语句,提交给数据库。(并不会提交事务)
4. 在检查以上2、3步的同时会检查此持久化对象的关联对象(就是有关系的对象(一对多,多对多等)),是否有级联操作,如果有就级联操作此关联的对象。还会检查是否对此关联对象执行关系操作,如果有就维护此对象与此关联对象的关系。
flush()并不会提交事务,也不会清空session对象一级缓存中的所有持久化对象。
其实在commit()事务提交方法中就先调用了flush方法。
代码示例1:()
//测试flush方法,将缓存中的所有对象,以相应的方式更新到数据库中
@Test
public void testFlush(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
Student stu5 = new Student("五五",26);
session.save(stu5);
//此处只会生成以上语句的insert sql语句,并将此语句提交给数据库
session.flush();
//提交事务
transaction.commit();
session.close();
}
代码示例2:
//批量操作
@Test
public void testFlush2(){
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
//向数据库中 插入1百万条数据
for(int i=0; i<1000000; i++){
Student stu = new Student("abc",10);
session.save(stu);
//隔50条就向数据库提交此sql语句并清空session一级缓存中的数据(不然数据量太大有可能内存溢出)
if(i%50 == 0){
//将内存中的数据按一定的sql语句提交给数据库(通过内存中的对象更新数据库中的数据)
session.flush();
//清除session一级缓存中的所有持久化对象
session.clear();
}
}
//提交事务
transaction.commit();
session.close();
}
二级缓存中存储的都是共有的数据(就是所有的线程都能够访问)。
存储在二级缓存中的数据不能频繁更新。
而且存储在二级缓存中的数据不能是某一个线程私有的数据。
二级缓存在hibernate中默认是关闭的,需手动开启
手动开启hibernate的二级缓存,在hibernate.cfg.xml配置文件中(必须先导入ehcache-1.5.0.jar 工具包(hibernate的基本jar包有此包))
<!-- 开启二级缓存 -->
<property name="cache.use_second_level_cache">true</property>
<!-- 配置二级缓存的提供商 -->
<property name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
如果某一个持久化对象要存储到二级缓存中,就必须对此持久化类的映射配置文件 进行配置。
方式一:
在hibernate.cfg.xml配置文件中配置要存储在二级缓存中的持久化类。
<class-cache usage=”read-only” class=””> class的值为要存储在二级缓存中的对象的完整类名。
方式二:(推荐使用)
在要存储到二级缓存对象的持久化类的映射配置文件中的ID标签前面配置以下标签:
<cache usage=”read-only”/>
以上标签的usage属性是缓存策略。直接设置为read-only即可
hibernate中的二级缓存是在sessionFactory对象中,所以二级缓存的生命周期和sessionFactory对象的生命周期一致,当加载hibernate.cfg.xml配置文件时创建,当从服务器上卸载此应用时被摧毁.
如果一个持久化对象在配置文件配置了可以存储在二级缓存,那么在使用save、update、load、get方法时会自动存储在二级缓存中。
当使用get或load方法通过主键获取某个表中的某个对象时,hibernate先会到一级缓存中去查找,如果没有再会到二级缓存中去查找,如果还没有,才到数据库去查找。(前提是这个对象必须可以存储在二级缓存中)
//Student类已经被配置为可以存储在二级缓存中
@Test//二级缓存的测试
public void test(){
Session session = sessionFactory.openSession();
//查询出主键为1的学生
Student stu1 = (Student)session.get(Student.class, 1);
session.clear();
//换班session对象//说明这个session的一级缓存被关闭了
session.close();
//再打开一个session对象
session = sessionFactory.openSession();
//再次查询主键为1的学生(此处不会生成sql语句,因为是从二级缓存中查询出来的)
stu1 = (Student)session.get(Student.class, 1);
System.out.println(stu1.getName());
session.close();
//以上只会产生一条sql语句,说明了stu1这个对象被添加到了二级缓存中,第二次查询此对象时,直接到二级缓存中去查找的此对象
}
查询缓存是建立在二级缓存之上的,也就是说必须要开启二级缓存,并且将要存储在二级缓存的对象的映射配置文件中的最前端 配置<cache usage=”read-only”/>标签表示此对象可以被存储在二级缓存中。
除了要做开启二级缓存的事项外,并且必须还要手动开启查询缓存,在hibernate.cfg.xml配置文件中用以下标签进行开启查询缓存:
<!-- 开启查询缓存 -->
<property name="cache.use_query_cache">true</property>
Query query = session.createQuery(“from Student”);//设置hql语句,并得到一个Query对象
query.setCacheable(true);//此语句表示先通过此hql语句为key到二级缓存的Map集合中去查询这个list集合,如果没有就从数据库中查询出所有的学生将其转换为list集合,并将此学生的list集合以此hql语句为key(Map<Student,List>)存储到二级缓存的一个Map集合中。
query.list();//返回此list集合。
那么这个list集合就以此hql语句为key被存储在了二级缓存的Map集合中。如果下次再用此hql语句查询,那么就会直接到此二级缓存中的Map集合中去取,前提是必须设置Query对象的setCacheable()方法为true;
//Student类已经被配置为可以存储在二级缓存中
//必须在二级缓存的基础上再在hibernate.cfg.xml配置文件设置
//<property name="cache.use_query_cache">true</property>表示手动开启查询缓存
//查询缓存测试
@Test
public void testCreateQuery(){
Session session = sessionFactory.openSession();
//设置查询所有学生的hql语句
Query query = session.createQuery("from Student");
//先到二级缓存中去查找此值,如果没有就到数据库中去查找此hql语句的值,并将此结果存储到二级缓存中。(这里会从数据库中查找此hql语句的值,并存储在二级缓存中)
query.setCacheable(true);
//将这个查询到的结果以List集合的形式返回
List<Student> students1 = query.list();
//关闭session对象
session.close();
//再开启一个新的session对象
session = sessionFactory.openSession();
//再次设置查询所有学生的hql语句
query = session.createQuery("from Student");
//表示先到二级缓存中去查找此值,如果没有就从数据库中去查找此值,并将查询到的值存储在二级缓存中
query.setCacheable(true);
//应为“from Student”的hql语句的值已经被存储在了查询缓存中,所以此处不会到数据库中查找此hql语句的值。
List<Student> students2 = query.list();
session.close();
}
如果我们要存储在二级缓存的数据相当大,那么我们就可以将此数据存储到磁盘上.
比如一个类的对象需要存储在二级缓存的数量相当大,那么我们就可以将一定的对象从二级缓存中取出存储在磁盘上。(比如一个对象在通过ehcache.xml配置文件,设置此对象在二级缓存中最大存储数量为5个,那么二级缓存中的此对象超出5个后,将把溢出的对象存储在磁盘上)
如果要将一个持久化类的对象存储在磁盘上,那么这个类必须能存储在二级缓存上(所以需要配置此持久化类能在二级缓存中存储,可以不开启查询缓存);
存储在磁盘上的对象,还是以从二级缓存中取出数据的方式取出此磁盘上的对象数据.
要将一定的二级缓存中的对象存储在磁盘上,必须在WEB-INF/classes目录下(对应的开发目录为src),下配置一个配置文件;
此配置文件名必须为:ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!--复制此模板时,需将此模板中的所有中文字符删除(包括注释的了)-->
<!-- 设置将数据存储到磁盘的目录 -->
<diskStore path="C:\\TEMP1"/>
<!-- 给以下属性配置默认的值 -->
<defaultCache
maxElementsInMemory="12"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>
<!-- 针对Student这个类来配置-->
<Cache
name="cn.domain.Student"<!--设置要被缓存的持久化类的全名-->
maxElementsInMemory="5" <!--设置二级缓存中此Student对象的最大数目,也就是说,当此持久化对象在二级缓存中的数量超过此数目时,将把移除的对象存储在磁盘上-->
eternal="false"<!--设置存储在磁盘上的Student对象是否为永久的,true表示永不过期,false表示超过timeToLiveSeconds属性的时间过期。 -->
timeToIdleSeconds="0"<!--设置对象空闲最长时间,以秒为单位,超过这个时间,对象过期,EHCache会把此对象中缓存中清除.如果此值为0表示此对象可以无限期处于空闲状态.-->
timeToLiveSeconds="0"<!--设置对象的生成最长时间,超过这个时间,对象过期,如果此值为0,表示对象可以无限期地存储在缓存中,该属性的最大值必须大于等于timeToIdleSeconds属性的值。-->
overflowToDisk="true"<!--当内存中的Student对象达到maxElementsInMemory属性的上限后,是否把溢出的对象写到基于硬盘的缓存中,true为写入到磁盘中,false为不写入到磁盘中-->
maxElementsOnDisk="10000000"<!--在磁盘上存储此Student对象的最大值。-->
diskPersistent="false"<!--当JVM结束时是否在磁盘上持久化Student对象true表示持久化,false不持久化,默认是false -->
diskExpiryThreadIntervalSeconds="120"<!--指定专门用于清除过期对象的监听线程的轮询时间。-->
memoryStoreEvictionPolicy="LRU"
/>
</ehcache>
//将二级缓存中的对象存储到磁盘上的代码测试
@Test
public void test(){
Session session = sessionFactory.openSession();
//从数据库中查询出所有的学生,并存放到一级缓存中、二级缓存中、
Query query = session.createQuery("from Student");
List<Student> students = query.list();
session.close();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
/*
* 此Student对象在二级缓存中的对象超出了5个,
* 那么将会把溢出的Student对象存储在ehcache.xml配置文件配置的目录下
* <diskStore path="E:\\TEMP1"/>//这里配置的是E:\\TEMP1目录
*/
}
hibernate的hql语句和sql语句都差不多,包括where(条件)、order by(排序)、group by(分组)、聚合函数、in(匹配)、like(模糊查询)、子查询、having(分组条件)等都可以使用,只是有少数地方不同.
from后面跟的不是表名,是持久化类的名称,返回的是持久化类的属性,也不是表中的字段。 执行hql语句是通过session.createQuery(“hql语句”);方式来执行的。
from Student 表示查询的是所有学生,如果以list集合返回,那么返回的list集合结构是:List<Student>
select sid, name from Student 返回自定义的字段,如果以list集合返回,那么返回的list集合结构是:List<Object[]>,数组中存储的是每一条记录的每一个字段
select new cn.domain.Student(sid, name) from Stduent; 返回自定义的字段,如果以list集合返回,那么返回的list集合结构是:List<Student>。返回的Student对象除了sid和name字段外,其他字段都为null,使用此此种方式还必须保证Student类中必须有此构造方法。
from Student where name=:studentName 此种方式是名称占位符的方式,必须在返回Query对象中设置此注入的值,如:query.setString(“studentName”,”张三”);
from Student where name=? 此中方式是”?”号占位符的方式,在JDBC中以用过,这里必须调用返回的Query对象的setString(Stirng是根据注入值的类型来改变的)方法进行注入”?”号的值,如:query.setString(0,””);注意”?”号的下标是从0开始的。
from Student where cid =(select cid from Classes where name=?); 子查询的方式(这里的”?”号需注入值)
hql语句还支持sql语句的很多功能,写法大概都一样。
query对象中有一个uniqueResult()方法,当返回的结果只有一条记录时,可以用此方法.但如果返回的结果有多条记录,用此方法会抛异常。
from Student s, Classes c where c.cid = s.classes.cid 返回的List结构是:List<Object[]> 每一个Object[]数组中都存放了Student对象和Classes 对象,如果要控制Object[]数组中存放的持久化对象的位置,只需要改变from后面的持久化类的顺序即可。
from Classes c inner join c.students s 返回的List结构是:List<Object[]>每一个Object[]数组中都存放了Student对象和Classes对象
, 如果要控制Object[]数组中存放的持久化对象的位置,只需要改变from后面的持久化类的顺序即可
from Student s left join s.classes c 返回的List结构是:List<Object[]>每一个Object[]数组中都存放了Student对象和Classes对象
, 如果要控制Object[]数组中存放的持久化对象的位置,只需要改变from后面的持久化类的顺序即可
迫切连接是将以上连接的值以持久化对象的方式返回,只需要在join关键字后面加上”fetch”关键字即可,如:
from Student s left join fetch s.classes c 那么返回的List集合的结构是:List<Student>,每个Student持久化对象中都有Classes持久化对象.
还可以转换持久化类的位置,如:
from Classes c left join fetch c.students s 那么返回的List集合的结构是:List<Classes>,每个Classes班级对象中都有Student学生对象。
select new cn.domain.Temp(s.name, c.name) from Classes c left join c.students s
那么返回的List集合的结构是:List<Temp>,每个Temp对象中都有学生名称和班级名称(Temp可以不是持久化类)
多表查询时,一般先找出中间表(核心表),然后通过中间表去查询其他的表(将中间表放在from关键字进跟着的后面。)。
多对多查询时,返回的List集合中的元素可能重复,所以要将这个List转换为HashSet,从而达到去重的目的。
一般只有from关键字紧跟着的类名的对象会出现重复,其他类的元素不会出现重复。
//hibernate的hql语句测试
public class CacheTest1 extends HibernateUtils {
//多对多的hql语句查询
//此种语句查询出来以后会有重复的现象,所以没查询出结果来必须去掉重复的(这里用的是将List集合转化为Set集合从而达到去重的目的)
@Test//查询出所有学生的所有课程
public void testHql1(){
Session session = sessionFactory.openSession();
List<Student> studentsList = session.createQuery("from Student s inner join fetch s.courses c").list();
//去重复,如果没有此语句查询出来的结果可能有重复的学生(这里重复的只有学生,而课程对象元素不会重复)。
Set<Student> students = new HashSet<Student>(studentsList);
//遍历这些学生的所有课程
for(Student stu : students){
System.out.print(stu+"\n ");
for(Course cou : stu.getCourses()){
System.out.print(cou);
}
System.out.println();
}
session.close();
}
@Test//查询出所有课程的学生
public void testHql2(){
Session session = sessionFactory.openSession();
List<Course> coursesList = session.createQuery("from Course c inner join fetch c.students").list();
//取掉重复的课程(这里重复的只是课程对象,课程中的学生不会重复)
Set<Course> courses = new HashSet(coursesList);
//遍历这些课程的所有学生
for(Course cou : courses){
System.out.print(cou+"\n ");
for(Student stu : cou.getStudents()){
System.out.print(stu);
}
System.out.println();
}
session.close();
}
//查询所有学生的班级及课程(一般向这种多表查询,首先找中间表,将中间表放在from关键字的第一个)
@Test
public void testHql3(){
Session session = sessionFactory.openSession();
List<Student> studentsList = session.createQuery("from Student s inner join fetch s.classes inner join fetch s.courses").list();
//去掉重复的学生
Set<Student> students = new HashSet<Student>(studentsList);
//遍历这个学生的班级及所有课程
for(Student stu : students){
System.out.println("学生:"+stu);
System.out.println("班级"+stu.getClasses());
for(Course cou : stu.getCourses()){
System.out.println("课程"+cou);
}
System.out.println("\n\n");
}
session.close();
}
}
hibernate中的分页只需要给返回的Query对象中设置相应的参数即可,再调用Query对象的list方法返回要查看页的记录。
@Test
public void pagingTest(){
Integer pageSize = 3;//每页显示的条数
Integer pageNumber = 3;//要看的页数
Session session = sessionFactory.openSession();
String sql = "from User";
Query query = session.createQuery(sql);
query.setFirstResult((pageNumber-1)*pageSize); //设置从第几列开始检索
query.setMaxResults(pageSize); //每页显示的最大条数
List<User> list = query.list();
for(User user : list){
System.out.println(user.getUid()+":"+user.getName()+":"+user.getPassword()+":"+user.getAge()+":"+user.getGender());
}
session.close();
}
标签:style http color io os 使用 ar java for
原文地址:http://blog.csdn.net/u013032887/article/details/39669025