Hibernate是数据访问层框架,对JDBC进行了封装,是针对数据库访问提出的面向对象的解决方案。使用Hibernate可以直接访问对象,Hibernate自动将此访问转换为SQL执行,从而达到间接访问数据库的目的。
1.共同点
1.都对JDBC进行了封装,都属于轻量级数据库持久层框架。 2.都采用ORM(Object Relational Mapping)思想解决了Entity和数据库的映射问题。 2.不同点
1.MyBatis采用SQL与Entity映射,对JDBC封装较轻;Hibernate采用数据库与Entity映射,对JDBC封装较重。 2.MyBatis需要自己写SQL,灵活性高;Hibernate自动生成SQL,开发效率高。 3.Hibernate的使用步骤
1.导入Hibernate相关JAR文件和数据库驱动JAR文件 2.创建Hibernate配置文件:hibernate.cfg.xml 3.创建实体类并使用Hibernate注解进行映射 4.使用Hibernate常用API执行增删改查等操作
环境搭建步骤如下:
- 导入Hibernate相关JAR包和MySQL数据库驱动JAR包文件。
- 在src类路径下创建Hibernate核心配置文件
Hibernate的配置文件是一个XML文件,通常命名为hibernate.cfg.xml。该文件中可以配置数据库连接参数、Hibernate框架参数以及映射关系文件或映射实体。
jdbc:mysql://localhost:3306/hibernate?serverTimezone=Aisa/Shanghai&useUnicode=true&characterEncoding=utf-8 com.mysql.cj.jdbc.Driver root root 20 1 5000 100 3000 2 true org.hibernate.dialect.MySQL57Dialect create true true
- 创建测试类
创建实体类:
/** * 学生实体类 * @author luckyliuqs */ @Entity @Table(name = "stu") public class Student implements Serializable{ private static final long serialVersionUID = 1L; @Id @Column(name = "stu_id") private Integer id; //Id @Column(name = "stu_name",length = 20,nullable = false) private String name; //姓名 @Column(name = "stu_sex") private Character sex; //性别 @Column(name = "stu_age") private Integer age; //年龄 @Column(name = "stu_addr") private String address; //地址 public Student() {} public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Character getSex() { return sex; } public void setSex(Character sex) { this.sex = sex; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } }
创建嵌入类:
/** * 地址嵌入类 * @author luckyliuqs */ @Embeddable public class Address { @Column(name = "detail",unique = true) private String detail; //地址详情 @Column(name = "postal_code") private String postalCode; //邮编 public String getDetail() { return detail; } public void setDetail(String detail) { this.detail = detail; } public String getPostalCode() { return postalCode; } public void setPostalCode(String postalCode) { this.postalCode = postalCode; } }
通过SessionFactory获取数据库连接的session
public class HibernateTest { public static void main(String[] args) { //1.创建Hibernate配置对象,用于加载Hibernate核心配置文件 Configuration cfg = new Configuration(); /** * 2.加载Hibernate核心配置文件 * 注意:默认加载src类路径下名为hibernate.cfg.xml文件 */ cfg.configure(); //3.创建SessionFactory对象 SessionFactory factory = cfg.buildSessionFactory(); //4.获取Session对象 Session session = factory.openSession(); System.out.println(session); } }
运行结果为:
可以看见,通过配置,Hibernate执行了创建表,获取到了Session。
如下:
@Entity 该注解在实体类上使用,用于表示该类为一个实体类,拥有该注解的类Hibernate框架会自动进行ORM映射 @Id 该注解在实体类属性定义上或属性对应的get访问方法上使用,用于表示该属性为主键属性
1.Hibernate要求一个实体类必须至少拥有一个主键属性
2.如果有两个和两个以上的主键属性,则要求实体类必须实现Serializable接口
@Table 该注解在实体类上使用,用于指定映射的数据库表的表名。默认表名为实体类的类名
@Table(name = "表名")
@Column 该注解在实体类属性上或属性对应的get访问方法上使用,用于指定表的字段。
name:定义字段名,默认字段名是属性的名称
length:定义字段长度,默认长度255
nullable:定义字段是否为空,true表示为空,false表示不为空。默认true。
unique:定义字段是否唯一性,true表示唯一,false表示不唯一。默认false。
insertable:定义是否允许字段可插入数据,true表示允许,false表示不允许,默认true。
updatable:定义字段是否允许字段可更新数据,true表示允许,false表示不允许,默认true。
@Column(name = "字段名", length=20, nullable=false, unique=true, insertable=true,updatable=true)
@Embeddable
该注解在嵌入类上使用,用于实体类引用嵌入类使用。
适用情况:很多实体类拥有共同的属性时,定义嵌入类,让实体类引用。
@Embedded
该注解效果等价于@Embeddable,在实体类的属性上使用。 @EmbeddedId 该注解在属性上使用,用于指定联合主键类。
注意:①联合主键类必须实现Serializable接口。②要有无参构造方法。③重写hashCode和equals方法
@GeneratedValue
该注解在属性上使用,用于指定主键生成策略。
strategy属性:属于JPA的注解
GenerationType.IDENTITY:根据底层数据库实现自增长
GenerationType.AUTO:跟GenerationType.TABLE策略一样
GenerationType.SEQUENCE:采用序列来实现自增长
GenerationType.TABLE:默认值,使用指定表来决定主键的取值,结合@TableGenerator注解使用
如:@GeneratedValue(strategy = GenerationType.IDENTITY)
generator属性:指定注解生成器的名字,通常跟Hibernate的@GenericGenerator注解一起使用。其值跟
@GenericGenerator注解的name属性值一致
@Id
@GeneratedValue(generator="id_gen")
@GenericGenerator(name="id_gen", strategy="xxx")
private Long id;
@GenericGenerator 该注解在属性上使用,用于指定Hibernate主键生成策略。
name属性:指定生成器的名称,需要跟@GeneratedValue注解的generator属性值一致
strategy属性:指定Hibernate主键生成策略,取值如下:
①identity:采用数据库自增长机制生成主键,适用于Oracle之外的其他数据库
②sequence:采用序列的方式生成主键,适用于Oracle数据库
③native:根据当前配置的数据库方言,自动选择sequence或identity
④assigned:不负责生成主键,将来由程序员编程处理主键生成
⑤uuid:采用uuid算法生成的一个主键,生成的主键值为一个不规则的长数字。
注意:uuid算法可以保证主键不重复,但是没有规律,因此将来不能按照主键进行排序,只能应用在字符串类型的主键属性上。
⑥increment:不是采用数据库自身的机制生成主键,而是Hibernate提供的一种生成主键的方式,它会通过获取当前表中主键的最大值,然后+1作为新的主键值
注意:在并发量高的时候,此方式可能会出现线程并发安全问题,从而生成重复的主键,因此不推荐使用。
@Transient 该注解在属性上使用,用于指定属性不会被映射成数据库表里面的字段,只是在程序中使用。 @OnToOne 关联注解,该注解在属性上或对应的get方法上使用,用于指定一对一关联关系 @OnToMany 关联注解,该注解在属性上或对应的get方法上使用,用于指定一对多关联关系 @ManyToOne 关联注解,该注解在属性上或对应的get方法上使用,用于指定多对一关联关系 @ManyToMany 关联注解,该注解在属性上或对应的get方法上使用,用于指定多对多关联关系 @Inheritance 该注解在实体类上使用,用于指定继承策略
strategy属性:取值如下:
InheritanceType.SINGLE_TABLE:表示所有子类一张表,使用单表继承策略。(建议)
InheritanceType.TABLE_PER_CLASS:表示每个子类一张表,主键不能使用自增方式,
InheritanceType.JOINED:表示父类一张表,子类一张表。父表中定义公共的字段,每个子表定义自己特有字段,字表通过主键(也是外键)引用父表的主键。
@DiscriminatorColumn
该注解在实体类上使用,用于指定单表继承鉴别列元信息,该注解常与@Inheritance注解一起使用
name属性:指定鉴别列的列名
discriminatorType:用于指定鉴别列的类型,取值如下:
DiscriminatorType.CHAR:字符型
DiscriminatorType.INTEGER:整数类型
DiscriminatorType.STRING:字符串类型(常用)
@DiscriminatorValue
该注解在实体类上使用,用于指定单表继承鉴别列的值。
注:单表继承策略时使用该注解
一级缓存
- 概念
Hibernate创建每个Session对象时,都会给该Session分配一块独立的缓存区,用于存放该Session查询出来的对象,这个分配给Session的缓存区称之为一级缓存,也叫Session级别的缓存。
- 使用一级缓存原因
Session取数据时,会优先向缓存区取数据,如果存在数据则直接返回,不存在则去数据库查询。这样降低了数据库的访问次数,提高了代码的运行效率。
- 特点
1.一级缓存是Session独享的, Session不共享缓存,每个Session不能访问其他Session的缓存区。 2.Session的save、update、delete操作会触发缓存更新。 3.缓存不能手动关闭,只能被清理 注意:Hibernate默认开启一级缓存,使用Hibernate框架必须使用其一级缓存特性。
- 清理缓存方法
void evict(Object obj)
将给定对象从一级缓存中驱逐
void clear() 将一级缓存中所有对象进行驱逐
void close() 关闭Session 二级缓存
- 概念
二级缓存类似于一级缓存,可以缓存对象,但它是SessionFactory级别的缓存,由SessionFactory负责管理啊。因此,二级缓存的数据是Session间共享的,不同的Session对象都可以共享二级缓存中的数据。
- 使用
①导入ehcache相关jar包:
②在Hibernate配置文件中开启二级缓存:
true org.hibernate.cache.ehache.internal.EhcacheRegionFactory ③在映射实体类上使用@Cache注解
该注解可以在类上、属性上和方法上使用,用于指定二级缓存策略。
usage属性指定二级缓存策略,取值如下:
read-only(只读型)常用 缓存不会更新,适用于不会发生改变的数据,效率最高,事务隔离级别最低。 read-write(读写型) 缓存会不定期更新,适用于变化频率低的数据。 nostrict-read-write(不严格读写型)常用 缓存会在数据变化时更新,适用于变化的数据。 transcational 缓存会在数据变化时更新,并支持事务,效率最低,事务隔离级别最高。 查询缓存
查询缓存又叫三级缓存,依赖于二级缓存,可以理解为特殊的二级缓存,也是SessionFactory级别的,也是SessionFactory负责维护。特点如下:
- 特点
查询缓存可以缓存任何查询到的结果
- 原理
查询缓存已hql为key、缓存该hql查询到的整个结果。即如果查询2次同样的hql,那么第二次执行时,此次查询可以从插叙你换成中取到第一次查询缓存的内容。
- 使用场景
频繁使用同样的hql做查询。
- 使用
在Hibernate配置文件中,配置使用查询缓存的配置如下:
//开启查询缓存
true 然后在执行HQL查询时,调用query.setCacheable(true)方法,开启此次查询使用查询缓存。
1.添加
save()
2.删除
delete()
3.更新
update()
4.查询
4.1单个对象查询
get() 非延迟加载获取指定对象 load() 延迟加载获取指定对象 4.2多个对象查询
list()和iterate()。iterate()具有延迟加载。
概念
在使用某些Hibernate方法查询数据时,Hibernate返回的只是一个空对象,并没有真正的查询数据库,而是在使用该对象时才会触发查询数据库,并将查询到的数据注入到该对象中,这种查询时机推迟到对象访问的机制称之为延迟加载。
比如前面提到的Session的get()和load()方法:
- 非延迟加载的get()方法
/** * 测试Session的get和load方法 */ @Test public void test() { Session session = HibernateUtil.getSession(); Student stu = session.get(Student.class, 1); session.close(); }
运行效果如下:
- 延迟加载的load()方法
/** * 测试Session的get和load方法 */ @Test public void test() { Session session = HibernateUtil.getSession(); Student stu = session.load(Student.class, 1); session.close(); }
运行效果如下:
可以看出,load的延迟加载,在对象没有使用的时候没有去数据库进行查询操作。
在Hibernate中,可以把实体对象看成有3种转态,分别为临时态、持久态和游离态。如下所示:
上述调用的都是Session的方法。garbage表示垃圾回收器回收对象。描述如下:
临时态 通过new创建的对象处于临时态。
持久态 持久态的对象与Session进行了关联。
调用Session的get()、load()、list()和iterate()方法获取的对象直接是持久态。
垃圾回收器不能回收持久态的对象
游离态 调用Session的evict()、clear()和close()方法,将对象从持久态变为游离态。 特点:临时态和游离态的对象未与Session进行关联,可以被垃圾回收。 对象与Session关联后,具有持久化的能力(修改会修改数据库对应的值)。如持久态:
/** * 测试对象从临时态到持久态的转换 */ @Test public void test1() { Session session = HibernateUtil.getSession(); session.beginTransaction(); Student stu = new Student(); stu.setName("Yoyo"); stu.setAge(17); stu.setSex('女'); stu.setAddress("xxx市"); //对象从临时态变为持久态 session.save(stu); //由于stu对象为持久态具有持久化的能力,因此修改对象属性的值也等于修改数据库列的值 stu.setAge(10); session.getTransaction().commit(); }
运行后,数据库中储存该学生的年龄信息为10,而不是17.
1.关联注解
@OneToOne 该注解在类属性上或者get方法上使用,用于指定一对一的关联关系。
cascade属性:指定级联操作,其值如下:
CascadeType.ALL:表示拥有所有的级联操作。
fetch属性:指定抓取策略,只针对查询操作。其值如下:fetch属性默认为EAGER
FetchType.EAGER:EAGER(渴望的),表示没有使用的时候都会查出来
FetchType.LAZY:懒加载,使用的时候才会查询。
如:@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@OneToMany 该注解在类属性上或者get方法上使用,用于指定一对多的关联关系
其属性和@OneToOne一样。fetch属性默认为LAZY
@ManyToOne 该注解在类属性上或者get方法上使用,用于指定多对一的关联关系
其属性和@OneToOne一样。fetch属性默认为EAGER
@ManyToMany 该注解在类属性上或者get方法上使用,用于指定多对多的关联关系
其属性和@OneToOne一样。fetch属性默认为LAZY
@JoinColumn 该注解在类属性上或get方法上使用,用于指定关联外键列的元信息。
name属性:指定外键列的列名,默认的外键列名为主控方中的关联被控方的属性名+"_"+被控方的主键属性名。
如:@JoinColumn(name = "wife_id")
@JoinTable 该注解在类属性上或get方法上使用,用于指定多对多关系关系表元信息。
name属性:指定多对多关系表的表名。
joinColumns属性:指定关系表关联自己的外键列的元信息
inverseJoinColumn属性:指定关系表关联对方的外键列的元信息
如:
@JoinTable(name="teacher_student",joinColumns {@JoinColumn(name="studentId")},inverseJoinColumns = {@JoinColumn(name="teacherId")})
private Set
stus; 2.单向关联
3.双向关联
一对一和多对多双向关联,其中一方交出关系维护权;多对一和一对多双向关联,"一方"交出关系维护权(不是多方交)。
- mappedBy属性
mappedBy的意思就是“被映射”,用于交出关系的维护权,即mappedBy这方不用管关联关系,关联关系交给另一方处理。只有OnToOne、OneToMany和ManyToMany上才有mappedBy属性,ManyToOne不存在该属性;
mappedBy的值为对方关联己方的属性名。
概念
HQL(Hibernate Query Language)属于Hibernate的查询语言。
与SQL区别
SQL面向表,HQL面向实体类。HQL中的?从0开始,JDBC的?从1开始。HQL中可以追加查询条件,条件中写的是实体类属性名,之后在执行查询前用query对象为条件参数赋值,例如:
Query
query = session.createQuery(“from Emp where name sex = ?”,Emp.class); query.setString(0,1); List emps = query.list(); 使用
- 查询所有字段
查询所有字段可以省略前面的select语句,query.list()方法返回的集合中封装的是实体对象。如下所示:
@Test public void get() { Session session = HibernateUtil.getSession(); String hql = "from Emp where sex = ?0 and ename like ?1"; Query
query = session.createQuery(hql, Emp.class); query.setParameter(0, 2); query.setParameter(1, "%O%"); List empList = query.list(); for (Emp emp : empList) { System.out.println(emp); } session.close(); }
- 查询部分字段
使用HQL查询表中部分字段时,需要在from前面追加select语句,并指定需要查询列对应的属性名,例如:
String hql = “select name,age from Emp”;
注意:当显示的查询部分字段时,query.list()方法返回的集合中封装的不再是实体对象,而是Object[]数组对象,数组中的值的顺序与select语句后查询字段的顺序一致。如下所示:
@Test public void get2() { Session session = HibernateUtil.getSession(); String hql = "select eno,ename from Emp "; Query
测试打印结果如下:可以看出,前面的是eno,后面的是ename,与查询字段的顺序一致
7369 SMITH 7499 ALLEN 7521 WARD 7566 JONES 7654 MARTIN
Hibernate的分页查询不是通过HQL条件实现,而是通过提供的方法实现,可以通过调用方法来设置分页的起点和每页显示的行数,例如:
@Test public void test() { Session session = HibernateUtil.getSession(); String hql = "from Emp where sex = :sex order by age asc"; Query
query = session.createQuery(hql, Emp.class); query.setParameter("sex", 2); //页码 int page = 1; //每页数据量 int pageSize = 2; //记录开始下标 int first = (page - 1) * pageSize; //设置分页的起始位置,从0开始计数 query.setFirstResult(first); //设置每页数据量 query.setMaxResults(pageSize); List emps = query.list(); for (Emp emp : emps) { System.out.println(emp); } } 注意:Hibernate中行数的起点从0开始计算,不同于JDBC中的从1开始计算
定义
Hibernate支持使用HQL进行多表联合查询,不过HQL中写的是关联对象及属性名
注意事项
1.使用HQL进行关联查询时,query.list()方法返回的集合中封装的不再是实体对象,而是Object[]数组对象。 2.使用HQL进行关联查询时,其抓取策略不受利用@Fetch注解指定的抓取策略的影响,默认为两条select语句进行查询。若要进行join方式关联查询,需要通过HQL实现多表联合查询。 实现多表联合查询方式
Emp代表员工实体类,Dept代表部门实体类
- 对象方式关联
如下所示:
String hql = “select e.id, e.name, e.age, e.sex, e.salary, d.id, d.name from Emp e,Dept d where e.dept.id = d.id”
- join方式关联
如下所示:Emp实体类中有dept(部门)属性
String hql = “select e.id, e.name, e.age, e.sex, e.salary, d.id, d.name from Emp e inner join e.dept d”
注意: join时,不能直接join对象,需要join自身关联的属性
- select子句关联
如下所示:
String hql = “select id, name, age, sex, salary, dept.id, dept.name from Emp”
- 直接使用SQL查询
若业务太复杂,无法通过HQL实现查询功能,Hibernate也支持使用SQL进行查询,参考代码如下:
SQLQuery sqlQuery = session.createSQLQuery("select e.id,e.name,e.dept_id from emp e,dept d where e.dept_id = d.id"); //将结果类型封装成指定的类型,默认结果类型为Object[]数组类型 sqlQuery.addEntity(Emp.class);
注意事项
1.需要在Controller控制器类的控制方法的实体Bean前添加@Valid或者@Validated注解,表示该实体Bean参数需要被校验。若想要使用分组校验,则只能使用@Validated注解来进行特定组校验。
2.添加Errors或者BindingResult参数,用来接收校验的错误信息,必须紧跟在校验的实体Bean参数后面。
3.所有的校验注解都必须配合@NotNull校验注解一起使用,因为若元素为空值(NULL),则除了@NotNull以外的校验注解都会失效。
4.被@Valid或者@Validated注解的实体Bean参数,Spring默认会将该实体Bean参数放入到模型中,默认的模型名为实体Bean类名首字母小写,将来视图组件(如jsp)可以通过该模型名获取对应的模型数据。
常用注解
这些注解位于javax.validation.constraints包中。
- @Null
用于校验被验证的属性值为空值(NULL),该注解在属性定义前或对应的get方法上使用,
- @NotNull
用于校验被验证的属性值不为空值(NULL),该注解在属性定义前或对应的get方法上使用,
- @NotEmpty
用于校验被验证的字符串或集合不为空值(NULL)或者空字符串(""),该注解在属性定义前或对应的get方法上使用,
- @NotBlank
用于校验被验证的字符串不能为空值或者空字符串(去掉首尾空白),该注解在属性定义前或对应的get方法上使用,
@NotBlank(message = "用户名不能为空") private String username;
- @Size
用于校验被验证的字符串长度符合指定的规则,校验集合中元素的个数符合指定的规则。会去掉首尾空白
有两个属性:min指定最小长度,max指定最大长度。
@Size(min = 6,max = 10,message = "密码长度必须介于{min}-{max}之间") private String password;
- @Pattern
被校验的元素必须符合指定的正则表达式规则。如下:
@Pattern(regexp = "[A-Z][A-Za-z0-9]{5,14]") private String username;
- @Min
被校验的元素必须是数字(整数),其值必须大于等于指定的最小值。
- @Max
被校验的元素必须是数字(整数),其值必须小于等于指定的最大值。
- @DecimalMin
被校验的元素必须是数字(double或BigDecimal),其值必须大于等于指定的最小值。
- @DecimalMax
被校验的元素必须是数字(double或BigDecimal),其值必须小于等于指定的最大值。
- @Digts(integer, fraction)
被校验的元素必须是数字,其值必须在可接受的范围。integer指定整数位位数,fraction指定小数位位数。
- @Past
- @Future
自定义校验注解
分组校验
当多个操作共用同一个实体类时,使用@Validated注解来进行特定组校验。