Hibernate入门简介
Hibernate环境搭建
- 加入hibernate框架相关的jar包(lib/required)
- 编写hibernate的配置文件:默认文件名称 hibernate.cfg.xml
- 创建src/hibernate.cfg.xml(hibernate.properties)
- xml文件编写规则(.dtd或者.xsd),使用dtd文件
- 如果xml文件中不能智能提示,则需要配置dtd文件
- 配置<session-factory>节点:如数据库连接信息
配置文件
- 采用mysql-connector-java-8.0.13.jar,xml无问题,可能是此包版本问题
<!-- 采用mysql-connector-java-8.0.13 -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<!-- session-factory代表一个数据库:配置property和mapping -->
<session-factory>
<!-- 方言:指定hibernate要生成何种数据库的SQL语句 -->
<property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>
<!-- 数据库连接 -->
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false</property>
<property name="connection.username">root</property>
<property name="connection.password">密码</property>
<!-- 打印SQL语句 -->
<property name="show_sql">true</property>
<!-- 自动生成表结构 -->
<property name="hbm2ddl.auto">update</property>
<!-- mapping映射(xml或注解在哪) -->
<mapping class="com.bfs.entity.User"/>
</session-factory>
</hibernate-configuration>
编写实体类(不需要建表)
- 在类上使用注解:@Entity, @Table
- 在属性上使用注解:@Column
@Entity
@Table(name = "tb_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 自增
private Long id;
// String类型映射到数据库字段的长度,默认是255
@Column(length = 50)
private String name;
private int age;
// 省略setter、getter方法
}
测试
- 增加一条记录
- 按主键查询
public class Test3 {
@Test
public void test1() {
Session s = HibernateUtil.openSession();
Transaction tx = s.beginTransaction();
try {
User u = new User();
u.setName("李四");
u.setAge(20);
s.saveOrUpdate(u);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
HibernateUtil.closeSession();
}
}
@Test
public void query1() {
Session s = HibernateUtil.openSession();
String hql = "from User where age>=?1";
List<User> list = s.createQuery(hql, User.class).setParameter(1, 20).list();
for(User u : list) {
System.out.println(u);
}
HibernateUtil.closeSession();
}
@Test
public void query2() {
Session s = HibernateUtil.openSession();
String sql = "select * from tb_user where age>=?0";
List<User> list = s.createNativeQuery(sql, User.class).setParameter(0, 20).list();
for(User u : list) {
System.out.println(u);
}
HibernateUtil.closeSession();
}
@Test
public void query3() {
Session s = HibernateUtil.openSession();
CriteriaBuilder cb = s.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> root = query.from(User.class);
query.select(root);
query.where(cb.ge(root.get("age"), 20));
List<User> list = s.createQuery(query).list();
for(User u : list) {
System.out.println(u);
}
HibernateUtil.closeSession();
}
@AfterClass
public static void destory() {
HibernateUtil.destory();
}
}
Hibernate核心概念
常用类
- SessionFactory
- 它表示一个数据库(连接信息)
- 它是重要级对象,创建比较耗时
- 线程安全的,可以全局共享(static)
- 一般不需要关闭
- Session
- 它表示一次连接(包装Connection对象)
- 它是轻量级对象,用完需要关闭
- 线程不安全的,但在线程内部应该尽可能共享(ThreadLocal)
- Transaction
- 事务(事务提交、事务回滚)
- 增删改操作,需要使用事务
- 查询操作,可以不使用事务
SessionFactory创建方式
- hibernate3.x和hibernate5.x中支持
SessionFactory factory = new Configuration().configure().buildSessionFactory();
- hibernate4.x和hibernate5.x中支持
ServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build();
Metadata metadata = new MetadataSources(registry).buildMetadata();
SessionFactory factory = metadata.buildSessionFactory();
HibernateUtil工具类
- 初始化SessionFactory
- 尽可能复用Session
- ThreadLocal的用法
public class HibernateUtil {
private static SessionFactory factory;
static {
factory = new Configuration().configure().buildSessionFactory();
}
// 线程局部变量
private static ThreadLocal<Session> session = new ThreadLocal<>();
/**
* 打开Session:线程不安全的,线程内部尽可能共享(线程变量)
* 1) 先从线程变量中获得
* 2) 如果没有再从factory中获得,并放在线程变量中
* */
public static Session openSession() {
Session s = session.get();
if(s==null || !s.isOpen()) {
s = factory.openSession();
session.set(s);
}
return s;
}
/**
* 关闭Session
* */
public static void closeSession() {
Session s = session.get();
session.set(null);
if(s!=null && s.isOpen()) {
s.close();
}
}
/**
* 关闭SessionFactory
* */
public static void destory() {
if(factory.isOpen()) {
factory.close();
}
}
}
基本注解
@Table
- @Table(name="xx"):指定映射的表名
-
如果不指定默认表名为类名
-
表名尽量不要有大写字母
-
oracle:不区分大小写(自动变成大写)
-
mysql:默认window不区分大小写,但linux区分大小写
-
不可用'-'等等
- @Table(indexes={@Index(columnList="name,age")}) 执行索引
- 索引:对查询性能影响很大
- 备注:通常直接在数据库表中增加索引
@Entity
表明实体
@Entity
@Table(name = "my_user", indexes = { @Index(columnList = "name"), @Index(columnList = "age") })
public class User {
@Column
默认所有属性上都有@Column,即所有属性都对应到数据库的某个字段
-
@Column(name=""):映射到数据库的字段名称
-
@Column(length=255):数据库字段的字符长度(字符串类型默认长度为255)
-
@Column(nullable=false, unique=true):非空约束,唯一性约束(会增加索引)
// unique唯一性约束, 会增加索引 @Column(length=255, nullable=false, unique=false) private String name;
@Transient
表示属性,但是不映射到数据库的字段
// 临时属性,数据库不存储该值
@Transient
private int myage;
主键生成策略
@Id
指定某属性是主键,一般使用String或Long, Integer类型
主键生成策略默认assigned(手工赋值)
@GeneratedValue生成策略
- 如果不指定生成策略,则使用AUTO
- 使用JPA自带的生成策略:strategy=共有4种取值
- IDENTITY:自增,最常用(mysql, sqlserver等)
- AUTO:等同SEQUENCE(如果数据库不支持序列,则通过表模拟),oracle中,使用自定义sequence生成策略
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
自定义(hibernate提供)生成策略
@SequenceGenerator+@GeneratedValue
/**
* oracle数据库表进行映射:
* 主键ID自增:使用sequence,并且每张表一个sequence
* 使用自定义生成器@SequenceGenerator
*/
@Entity
@Table(name = "my_user2")
@SequenceGenerator(name = "seq_user", sequenceName = "S_USER", allocationSize = 1) // 定义生成器
public class User2 {
@Id
@GeneratedValue(generator = "seq_user")
private Long id;
@GenericGenerator+@GeneratedValue
strategy取值情况
-
native: 等同于strategy=IDENTITY
-
sequence:等同于@SequenceGenerator
@Entity
@Table(name = "my_user3")
@GenericGenerator(name="mynative", strategy="native") //也可以在其它类中使用
public class User3 {
@Id
@GeneratedValue(generator = "mynative")
private Long id;
- uuid:主键是字符串,使用hibernate生成uuid
@Entity
@Table(name = "my_user4")
@GenericGenerator(name="myuuid", strategy="uuid") //也可以在其它类中使用
public class User4 {
@Id
@GeneratedValue(generator = "myuuid")
@Column(length = 36)
private String id;
- guid:主键是字符串,使用数据库生成uuid(select uuid())
@Entity
@Table(name = "my_user5")
@GenericGenerator(name="myguid", strategy="guid")
public class User5 {
@Id
@GeneratedValue(generator = "myguid") //可以使用其它类定义的生成器
@Column(length = 36)
private String id;
-
assigned:不使用主键生成,需要手工赋值
-
foreign:在一对一关联关系时,可能用到
uuid和guid的区别
- uuid: 由hibernate生成,有一定的顺序, 推荐使用
- guid: 由数据库生成,select uuid(),无序
属性使用基本类型还是包装类型
- 主键要使用包装类型:Long/Integer
- 普通属性,使用包装类型(Integer)总体要比基本类型(int)好
- Integer:数据库字段可以为空
建议使用
查询条件:select * from tb where status is not null and status!=1
- int:数据库字段不能为空(不能将null赋值给int)
int类型属性有默认值0
查询条件:不需要判断是否为空
主键使用整型还是字符串
- 主键自增(整型):
- 优点:占空间小,性能好一点,有顺序,比较直观
- 不足:分布式多库部署存在问题(合库非常麻烦)
- uuid(字符串型):
- 优点:兼容所有数据库,适合分布式多库部署
- 不足:相比整型而言,占空间大一点,性能差一点,无顺序,不直观
主键策略的选择
- mysql, sqlserver:
@GeneratedValue(strategy=GenerationType.IDENTITY)
- oracle:
@SequenceGenerator(name = "seq_user", sequenceName = "S_USER", allocationSize = 1)
@GeneratedValue(generator = "seq_user")
注意:一般每张表定义一个生成器
- 通用uuid:
@GenericGenerator(name="myuuid", strategy="uuid")
@GeneratedValue(generator = "myuuid")
注意:uuid可以实体间共享
Hibernate关联关系映射
关联关系
- 一对多:一个班有多个学生
- 多对一:一个学生只属于一个班级
- 一对一:一个人只有一张身份证(一张身份证只属于一个人)
- 多对多:一个学生选择多门课程,一门课程有多个学生(数据库中要拆分成2个多对一)
单向关联关系和双向关联关系
- 双向关系:通常使用双向关联(关系不紧密时,可使用单向关系)
-
单向关系:
- 优先使用单向的多对一
- 单向的一对多: 性能较差,由“一”的一方来维护双方关系
多向一对多
- @OneToMany(mappedBy="clazz")
-
one:指自身类(本类),many:指属性,前面是自身类,后面的是所标注的属性
-
mappedBy="属性类的属性名":在自身类(班级)中放弃维护与被标注属性(学生)的关系,参照属性类(学生)中的clazz属性,one放弃维护与many的关系
-
在c.students.add(学生)的时候不发SQL。因为设置mappedBy="xx",自身类不维护关联关系
// one指自身(本类) many指属性, 前面是自身 后面的是属性
// 在班级中放弃维护与学生的关系,**参照学生中的clazz属性**
@OneToMany(mappedBy="clazz")
private Set<Student> students = new HashSet<>();
- @ManyToOne @JoinColumn(name = "clazz_id")
-
@ManyToOne:many是自身类(学生),one是属性(clazz)
-
@JoinColumn(name = "clazz_id"):在自身类(学生)中加入一列(id) 来代替clazz对象的存储
- 级联关系 cascade
- 类型
- CascadeType.ALL
- CascadeType.PERSIST
- CascadeType.MERGE
- CascadeType.REMOVE 级联删除
- CascadeType.REFRESH
- CascadeType.DETACH
- 在@OneToMany @ManyToOne都要加上(多向一对多)
@OneToMany(mappedBy = "comment" ,cascade = CascadeType.REMOVE)
private Set<Reply> replys = new HashSet<>();
@ManyToOne(cascade = CascadeType.REMOVE)
@JoinColumn(name = "comment_id")
private Comment comment;
单向多对一
在Clazz类中不设置学生属性(不维护学生属性)
// Student类
@ManyToOne
@JoinColumn(name = "clazz_id")
private Clazz clazz;
单向一对多
// Clazz类
@OneToMany //维护关联关系
@JoinColumn(name = "clazz_id")
private Set<Student> students = new HashSet<>();
一对一
基于外键的一对一
特殊的多对一,多方的外键增加唯一性约束
- 两边都使用@OneToOne,如果一边没有指定mappedBy="xxx"
- 那么相当于省略了:@JoinColumn(name="xx")
- Person
@Entity
@Table(name = "tb_person")
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 姓名
@Column(length = 50)
private String name;
// 拥有一张身份证
@OneToOne
@JoinColumn(name="card_id", unique=true)// tb_person多一列
private IdCard card;
}
- IdCard
@Entity
@Table(name = "tb_idcard")
public class IdCard {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 号码
@Column(length = 20)
private String sno;
// 只属于一个人
@OneToOne(mappedBy="card")
private Person person;
}
- 测试文件
public class T1 {
@Test
public void t1() {
Session session = HibernateUtil.openSession();
Transaction tx = session.beginTransaction();
Person p = new Person();
p.setName("张三");
IdCard card = new IdCard();
card.setSno("1234");
// 设置关联关系
p.setCard(card); //有用
// 下面语句没有作用,不发SQL。因为设置mappedBy="xx",不维护关联关系
card.setPerson(p);
// 保存对象:注意顺序
session.save(card);
session.save(p);
tx.commit();
HibernateUtil.closeSession();
}
@Test
public void t2() {
Session session = HibernateUtil.openSession();
String hql = "from Person";
List<Person> list = session.createQuery(hql, Person.class).list();
for(Person p : list) {
System.out.println(p.getName());
if(p.getCard() != null) {
System.out.println(p.getCard().getSno());
}
System.out.println("===========");
}
HibernateUtil.closeSession();
}
}
共享主键的一对一
- 主对象的ID自增,@PrimaryKeyJoinColumn
- 从对象的ID不能自增,需要参照主对象的ID
- Person
@Entity
@Table(name = "tb2_person")
public class Person2 {
// 主对象的ID自增
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 姓名
@Column(length = 50)
private String name;
// 拥有一张身份证
@OneToOne
@PrimaryKeyJoinColumn //默认是@JoinColumn
private IdCard card;
}
- IdCard
@Entity
@Table(name = "tb2_idcard")
public class IdCard2 {
// 从对象的ID不能自增,需要参照主对象的ID
@Id
@GeneratedValue(generator = "fk")
@GenericGenerator(
name="fk",
strategy="foreign",
parameters=@Parameter(name="property", value="person")
)
private Long id;
// 号码
@Column(length = 20)
private String sno;
// 只属于一个人
@OneToOne(mappedBy = "card")
private Person person;
}
多对多
生成中间表
- Student
@Entity
@Table(name = "tbx_student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 姓名
@Column(length = 50)
private String name;
// 有多门课
// 查询学生的课程:根据学生ID去中间表,和student_id匹配,再用查询到course_id和课程ID查询
@ManyToMany
@JoinTable(name="tbx_student_course",
joinColumns= @JoinColumn(name="student_id"),
inverseJoinColumns=@JoinColumn(name="course_id"))
private Set<Course> courses = new HashSet<>();
}
- Course
@Entity
@Table(name = "tbx_course")
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 名称
@Column(length = 50)
private String name;
// 有多个学生,放弃维护关联关系(不发SQL)
@ManyToMany(mappedBy = "courses")
private Set<Student> studnets = new HashSet<>();
}
- test
// 设置关联关系
s1.getCourses().add(c1); //有用
s1.getCourses().add(c2); //有用
s2.getCourses().add(c2); //有用
// 下面语句没有作用,不发SQL。因为设置mappedBy="xx",不维护关联关系
c1.getStudnets().add(s1);
c2.getStudnets().add(s2);
拆分2个一对多(手动中间表)
- Student
@Entity
@Table(name = "tbx2_student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 姓名
@Column(length = 50)
private String name;
// 有多个成绩
@OneToMany(mappedBy = "student")
private Set<Score> cjs = new HashSet<>();
}
- Course
@Entity
@Table(name = "tbx2_course")
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 名称
@Column(length = 50)
private String name;
/// 有多个成绩
@OneToMany(mappedBy = "course")
private Set<Score> cjs = new HashSet<>();
}
- Score(中间表)
/**
* 成绩表
*/
@Entity
@Table(name = "tbx2_score")
public class Score {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 属于一个学生
@ManyToOne
@JoinColumn(name = "student_id")
private Student2 student;
// 属于一门课程
@ManyToOne
@JoinColumn(name = "course_id")
private Course2 course;
// 分值
private int val;
}
Hibernate增删改查
分类
-
增加:save(), saveOrUpdate()
-
修改:update(), saveOrUpdate()
-
删除:delete() //按ID删除
-
查询:
- 按ID查询:get()/load()
- HQL查询(JPQL):推荐使用(类似SQL)
- 条件查询:不推荐使用(特殊繁琐)
- 原生SQL查询:特殊场合下使用
HQL
HQL查询语言
HQL类似于SQL,但完全不同,HQL是面向对象的
-
查询结果集
-
DML风格查询:
增加: insert into tb(x, y) select xx, yy from tb2
修改: update tb set x=xx
删除: delete from tb
HQL语句的大小写敏感问题
-
类名和属性名:大小写敏感,如: Student
-
其它:大小写不敏感,如: select, SELECT, Select, SElect
HQL占位符
- 位置占位符:
?1,?2,...
- setParameter(1, 25)
- 命名占位符:
:xxx
- setParameter("age", 25)
Session session = HibernateUtil.openSession();
//String hql = "from User where age>?1"; //位置占位符
//String hql = "from User u where u.age>?1";
String hql = "select u from User u where u.age>?1";
List<User> list = session.createQuery(hql, User.class).setParameter(1, 25).list();
for(User u : list) {
System.out.println(u);
}
// 命名占位符 :xx
String hql2 = "from User where age>:age and name like ?1";
List<User> list2 = session.createQuery(hql2, User.class)
.setParameter("age", 25)
.setParameter(1, "test%").list();
for(User u : list2) {
System.out.println(u);
}
HibernateUtil.closeSession();
增加 save(), saveOrUpdate()
save()
User u = new User();
u.setName("张三");
u.setAge(20);
session.save(u); //u.id的值由insert语句获得的,发SQL语句
saveOrUpdate()
数据库中存在有这个id的对象为更新,不存在为save
User4 u = new User4();
u.setName("张三");
u.setAge(20);
session.saveOrUpdate(u); //不发SQL,u.id的值由Hibernate生成
批量操作
for(int i=1; i<=20; i++) {
// 方式1:通过ID构造
//Clazz clazz = new Clazz();
//clazz.setId(i%2+1L);
// 方式2:从数据库查询
Clazz clazz = session.get(Clazz.class, i%2+1L);
// 学生
Student stu = new Student();
stu.setName("test_" + i);
stu.setAge(20 + i);
stu.setClazz(clazz); //保证clazz的id不为null
session.save(stu);
}
修改 update(), saveOrUpdate()
update()
Clazz c = session.get(Clazz.class, 1L);
c.setName("17-5");
session.update(c);
批量操作
Session session = HibernateUtil.openSession();
Transaction tx = session.beginTransaction();
// HQL的批量修改
String hql = "update Student set name=?1 where age>=?2";
session.createQuery(hql).setParameter(1, "测试").setParameter(2, 38).executeUpdate();
tx.commit();
HibernateUtil.closeSession();
删除 delete()
通过ID构造对象进行删除
Clazz c = new Clazz();
c.setId(1L);
session.delete(c);
查询数据库获得对象
Clazz c = session.get(Clazz.class, 1L);
session.delete(c);
HQL语句(批量)删除
Session session = HibernateUtil.openSession();
Transaction tx = session.beginTransaction();
// HQL的批量删除
String hql = "delete from Student where age>=?1";
session.createQuery(hql).setParameter(1, 38).executeUpdate();
tx.commit();
HibernateUtil.closeSession();
查询
原生SQL查询
/**
* 原生SQL查询
* */
@Test
public void query_sql() {
Session session = HibernateUtil.openSession();
// 位置占位符和命名占位符
String sql = "select * from tb_user where age>?1";
List<User> list = session.createNativeQuery(sql, User.class).setParameter(1, 25).list();
for(User u : list) {
System.out.println(u);
}
String sql2 = "select * from tb_user where age>:age";
List<User> list2 = session.createNativeQuery(sql2, User.class).setParameter("age", 28).list();
for(User u : list2) {
System.out.println(u);
}
HibernateUtil.closeSession();
}
Hibernate的条件查询(已过时)
/**
* Hibernate的条件查询(已过时)
* */
@Test
public void query_critera() {
Session session = HibernateUtil.openSession();
List<User> list = session.createCriteria(User.class).add(Restrictions.gt("age", 25)).list();
for(User u : list) {
System.out.println(u);
}
HibernateUtil.closeSession();
}
HQL单表查询
- 创建查询语句:
session.createQuery(hql, Student.class)
- 设置参数:
session.setParameter(1, 30)
- 得到查询列表:
session.list()
- 结果数组:
session.uniqueResult()
: 得到 Object[] arr; - 按照ID查询:
session.get(类型.class, 1L);
: 如果数据库没有该记录,则返回null,用到该对象时抛NullPointerException
- List
//String hql = "from Student where age>=?1";
//String hql = "from Student s where s.age>=?1";
String hql = "select s from Student s where s.age>=?1";
List<Student> list = session.createQuery(hql, Student.class).setParameter(1, 30).list();
- List<Object[]>
// 查询部分字段:每条记录是Object[]
String hql = "select id, name, age from Student where age>=?1";
List<Object[]> list = session.createQuery(hql, Object[].class).setParameter(1, 30).list();
- List
// 查询部分字段:每条记录是 List
String hql = "select new list(id, name, age) from Student where age>=?1";
List<List> list = session.createQuery(hql, List.class).setParameter(1, 30).list();
- List
// 查询部分字段:每条记录是 List
String hql = "select new map(id as id, name as name, age as age) from Student where age>=?1";
List<Map> list = session.createQuery(hql, Map.class).setParameter(1, 30).list();
HQL连接查询
- 逗号关联(存在笛卡尔积)
// 存在笛卡尔积
// String hql = "from Student, Clazz";
String hql = "select s, c from Student s, Clazz c";
List<Object[]> list = session.createQuery(hql, Object[].class).list();
- 逗号关联(解决笛卡尔积)
// 模仿sql,解决笛卡尔积(糟糕方式)
String hql = "from Student s, Clazz c where s.clazz.id=c.id and c.name=?1 and s.age<?2";
List<Object[]> list = session.createQuery(hql, Object[].class).setParameter(1, "17-1班").setParameter(2, 30).list();
- 对象隐式关联
// 隐式的关联:某班级中年龄<=30
String hql = "from Student where clazz.name=?1 and age<?2";
List<Student> list = session.createQuery(hql, Student.class).setParameter(1, "17-1班").setParameter(2, 30).list();
- 对象显式关联
// 显式的关联:连接查询
// 内连接:join, 外连接(左连接:left join、右连接:right join)
// sql的连接查询:select x.*, y.* from tb_student x join tb_clazz on x.clazz_id=y.id where ...
String hql = "from Student s left join s.clazz c where c.name=?1 and s.age<=?2";
// form前省略:select s, c
List<Object[]> list = session.createQuery(hql, Object[].class).setParameter(1, "17-1班").setParameter(2, 30).list();
- 内连接
// 连接查询(内连接)
String hql = "select s from Student s join s.clazz c where c.name=?1 and s.age<=?2";
List<Student> list = session.createQuery(hql, Student.class).setParameter(1, "17-1班").setParameter(2, 30).list();
HQL分页和排序
-
setFirstResult(0)
//从第几条记录开始 -
setMaxResults(5)
//每页大小
// 排序和分页
// 先按age降序排,如果age相同则再按name升序排
String hql = "from Student order by age desc, name asc";
List<Student> list = session.createQuery(hql, Student.class)
.setFirstResult(0) //从第几条记录开始, 从0开始算
.setMaxResults(5) //每页大小
.list();
HQL迫切连接查询
- join:连接查询,返回Object[]
- join fetch:迫切连接查询,返回单个对象,性能优化
// join:连接查询,返回Object[]
// join fetch:迫切连接查询,返回单个对象
// 迫切连接查询: join fetch(性能优化)
String hql = "from Student s join fetch s.clazz c where c.name=?1 and s.age<=?2";
// 返回Student集合,并将clazz对象填充到student中
List<Student> list = session.createQuery(hql, Student.class).setParameter(1, "17-1班").setParameter(2, 30).list();
聚合查询
一般与分组group by 一起使用
- count()
- avg()
- max()
- min()
- sum()
// 错误:String hql = "select * from Student";
String hql = "select count(*), min(age), max(age), avg(age), sum(age) from Student";
Object[] arr = session.createQuery(hql, Object[].class).uniqueResult();
System.out.println(Arrays.toString(arr));
查询函数(日期)
- JPQL标准函数
- concat(c1, c2, ...): 字符串拼接
// concat(c1, c2, c3):字符串拼接
String hql = "select concat(name, '_', age) from Student";
List<String> list = session.createQuery(hql, String.class).list();
- substring(c1, begin, len):begin从1开始的
// substring(c1, begin, len):begin从1开始的
String hql = "select substring(name, 2, 2) from Student";
List<String> list = session.createQuery(hql, String.class).list();
- upper(c), lower(c)
- trim(c), length(c)
- abs(c), mod(c), sqrt(c)
- current_date(), current_time()
// 时间相关函数current_date(), current_time(), current_timestamp()
String hql = "select current_date(), current_time(), name from Student";
List<Object[]> list = session.createQuery(hql, Object[].class).list();
- HQL函数
- cast(c as string):类型转换
- extract(year from c):抽取时间
- year(c), month(c), day(c)
- hour(c), minute(c), second(c)
// year(字段), month(), day(), hour(), minute(), second()
String hql = "select year(current_date), month(current_date()), name from Student";
List<Object[]> list = session.createQuery(hql, Object[].class).list();
- str(c):转成字符串
- 集合处理表达式
- size(集合)
// 查找没有学生的班级
String hql = "from Clazz where size(students)=0";
List<Clazz> list = session.createQuery(hql, Clazz.class).list();
- maxelement(集合), minelement(集合)
- [some|exists|all|any] elements(集合)
-
case表达式
CASE {operand} WHEN {test_value} THEN {match_result} ELSE {miss_result} EN
-
nullif表达式
select nullif(p.nick, p.name) from xxx
子查询
- where xx in(select xx from tb)
// HQL中,如果条件是对象,实质上指对象的id
String hql = "from Student where clazz in (select c from Clazz c)";
List<Student> list = session.createQuery(hql, Student.class).list();
分组查询
-
group by 对象 ==> group by 对象.id
-
group by 对象.属性 having 条件
-
where和having的区别
- where:在分组之前,过滤条件(不可以使用聚合函数)
- having:在分组之后,过滤条件(可以使用聚合函数)
- example1
String hql = "select s.clazz, count(*), min(s.age), max(s.age) from Student s group by s.clazz";
List<Object[]> list = session.createQuery(hql, Object[].class).list();
- example2
String hql = "select s.clazz, count(*), min(s.age), max(s.age) from Student s "
+ "group by s.clazz having max(s.age)>=?1";
List<Object[]> list = session.createQuery(hql, Object[].class).setParameter(1, 40).list();
- example3
// age>=30的学生,再进行分组
String hql = "select s.clazz, count(*), min(s.age), max(s.age) from Student s "
+ "where age>=30 group by s.clazz having max(s.age)>=?1";
List<Object[]> list = session.createQuery(hql, Object[].class).setParameter(1, 40).list();
对象的生命周期
生命周期状态
- 瞬时状态(Transient):使用new操作符得到的对象,没有和数据库表进行关联。(数据库中没有与之对应的纪录)
- 持久状态(Persist):持久对象是任何具有数据库标识的实例,它由Session统一管理。它们的状态在事务结束时同数据库进行同步。(数据库中有与之对应的纪录,并受session管理) 对持久状态对象的修改,会自动同步到数据库, 同步的同时(set方法)不发sql,在
commit()/flush()
时候统一发sql - 脱管状态(Detached):Session关闭或调用clear()或evict(),不受Session的管理。(数据库中有与之对应的纪录,但不受session管理)
@Test
public void t1() {
Session session = HibernateUtil.openSession();
Transaction tx = session.beginTransaction();
Clazz clazz = new Clazz();
clazz.setName("18-1");
// 此时,clazz对象是瞬时状态(id是null)
session.save(clazz);
// 此时,clazz对象是持久状态(id有值)
tx.commit();
HibernateUtil.closeSession();
}
@Test
public void t2() {
Session session = HibernateUtil.openSession();
Transaction tx = session.beginTransaction();
Clazz clazz = session.get(Clazz.class, 2L);
// 此时,clazz对象是持久状态(id有值)
clazz.setName("abcd");
// 注意:对持久状态对象的改变,会自动同步到数据库(可能导致严重问题)
// session.saveOrUpdate(clazz); //可选
tx.commit();
HibernateUtil.closeSession();
}
函数
- session.clear():将所有对象从session中逐出
Session session = HibernateUtil.openSession();
Transaction tx = session.beginTransaction();
Clazz clazz = session.get(Clazz.class, 2L);
// 此时,clazz对象是持久状态(id有值)
// 强制清空session,不进行刷新session(即不与数据库同步)
session.clear();
// 原session中的所有对象都变成脱管状态
// 此时,clazz对象是脱管状态,数据库中有与之对应的纪录但不受session管理
clazz.setName("1123");
// 不自动同步到数据库
// 通过update()重新变成持久状态
// session.saveOrUpdate(clazz);
tx.commit();
session.close();
- session.evict(obj):只将该对象从session中逐出, 不影响其它对象
Session session = HibernateUtil.openSession();
Transaction tx = session.beginTransaction();
Clazz clazz = session.get(Clazz.class, 2L);
// 此时,clazz对象是持久状态(id有值)
clazz.setName("aaaaaa");
// saveOrUpdate()此时不会发SQL语句
session.saveOrUpdate(clazz);
// 将clazz对象逐出session
session.evict(clazz);
tx.commit();
session.close();
- session.flush():强制刷新session,与数据库同步,发SQL语句
Session session = HibernateUtil.openSession();
Transaction tx = session.beginTransaction();
Clazz clazz = session.get(Clazz.class, 2L);
// 此时,clazz对象是持久状态(id有值)
clazz.setName("aaaaaa");
// 同步session中对象状态到数据库
session.flush(); //发SQL语句
// 将clazz对象逐出session
session.evict(clazz);
tx.commit();
session.close();
hibernate什么时候会SQL语句
-
save()
需要获得ID值,当有ID不会发SQL
User u = new User();
u.setName("张三");
u.setAge(20);
session.save(u); //u.id的值由insert语句获得的,发SQL语句
User4 u = new User4();
u.setName("张三");
u.setAge(20);
session.saveOrUpdate(u); //不发SQL,u.id的值由Hibernate生成
User5 u = new User5();
u.setName("张三");
u.setAge(20);
session.save(u); //u.id的值由数据库select uuid()生成,发SQL
-
commit()
前调用flush()
方法,刷新session,与数据库同步
Clazz clazz = session.get(Clazz.class, 1L);
// 此时,clazz对象是持久状态(id有值)
clazz.setName("aaaaaa"); // 不发sql
// 同步session中对象状态到数据库
session.flush(); //发SQL语句
clazz.setName("bbbb");
tx.commit();//发SQL
-
commit()
提交事务时(之前没有flush()
,或者flush()
后没有改变对象)
delete()
,update()
,save()有ID时
不会发SQL,而是等到commit()
统一发
Clazz clazz = session.get(Clazz.class, 1L);
// 此时,clazz对象是持久状态(id有值)
clazz.setName("aaaaaa"); // 不发sql
// 同步session中对象状态到数据库
session.flush(); //发SQL语句
//clazz.setName("bbbb");
tx.commit();//不发SQL
-
get()
方法查询数据库
其他注解
@Where(只映射满足条件的纪录)
用于类上,用于集合属性上
@Entity
@Table(name = "tb_clazz")
@Where(clause = "delFlag is null")
public class Clazz {
@OrderBy
用于集合属性上,按数据库表字段进行排序(简单排序)
@OrderBy("所标注属性的字段 desc, 所标注属性的字段 asc")
@OrderBy("age desc, name asc")
private Set<Student> students = new HashSet<>();
@SortComparator
用于集合属性上,按业务逻辑进行Java排序(复杂排序)
@SortComparator(StudentComparator.class)
private Set<Student> students = new HashSet<>();
Hibernate缓存
懒加载与迫切加载
get() load()
- get()
-
get():立即发SQL,返回Clazz类型对象
-
如果数据库没有该记录,则返回null,用到该对象时抛NullPointerException
// get():立即发SQL,返回Clazz类型对象
// 如果数据库没有该记录,则返回null,用到该对象时抛NullPointerException
Clazz clazz = session.get(Clazz.class, 1L);
- load()
-
load():不立即发SQL(延迟加载),返回一个代理对象
-
在必要的时候,才发SQL查询(查询到的结果放在对象的target属性上)如DB没有该记录,仍然会返回代理对象,用到该对象时抛ObjectNotFoundException
// load():不立即发SQL(延迟加载),返回一个代理对象
// 在必要的时候,才发SQL查询(查询到的结果放在对象的target属性上)
// 如果数据库没有该记录,仍然会返回代理对象,用到该对象时抛ObjectNotFoundException
Clazz clazz = session.load(Clazz.class, 1L);
懒加载(延迟加载)
-
一对多:默认使用延迟加载(懒加载), 在必要的时候,会发SQL加载
-
代表函数:
load()
-
注解:
@OneToMany(fetch=FetchType.LAZY)
不要改变"一对多"中默认的懒加载, 如果确实需要立即加载,使用HQL中的join fetch
@OneToMany(mappedBy = "clazz", fetch=FetchType.LAZY)// 默认LAZY
private Set<Student> students = new HashSet<>();
// 不要改变"1对多"中默认的懒加载, 如果确实需要立即加载,使用HQL中的join fetch
// join fetch:迫切连接
String hql = "select distinct c from Clazz c left join fetch c.students";
List<Clazz> list = session.createQuery(hql, Clazz.class).list();
迫切加载
-
多对一: 默认使用迫切加载
-
代表函数:
get()
-
注解:
@ManyToOne(fetch = FetchType.EAGER)
Student student = session.get(Student.class, 1L);//立即加载关联对象
一级缓存(session级缓存)
-
持久化对象,受session管理(持久化对象是放在session缓存中),所有查询出来的实体对象,一个一个地都放在session缓存中(不是集合对象),缓存只在同一个sessoin中有效
-
get()和load()会先优先从一级缓存中取值
-
session.close(), session.clear(), session.evict(obj)会影响session缓存
- example1: load()使用缓存
Session session = HibernateUtil.openSession();
// 发SQL
Clazz clazz = session.get(Clazz.class, 1L);
System.out.println(clazz.getName());
// 获得session缓存中的数据
Set keys = session.getStatistics().getEntityKeys();
System.out.println(keys);
// 不发SQL:使用缓存
Clazz clazz2 = session.load(Clazz.class, 1L);
System.out.println(clazz2.getName());
HibernateUtil.closeSession();
- example2: clear()影响缓存
// 清空session缓存
session.clear();
// 发SQL
Clazz clazz2 = session.load(Clazz.class, 1L);
System.out.println(clazz2.getName());
- example3: 个题对象形式存在
// 一个一个地都放在session缓存中(不是集合对象)
//查询到的结果集list中的每个实体对象,都会放在session缓存
String hql = "from Clazz";
List<Clazz> list = session.createQuery(hql, Clazz.class).list();
System.out.println(list.size());
// 查询到的结果集list中的每个实体对象,都会放在session缓存
// 获得session缓存中的数据
Set keys = session.getStatistics().getEntityKeys();
System.out.println(keys);
// 不发SQL:使用缓存
Clazz clazz2 = session.load(Clazz.class, 1L);
System.out.println(clazz2.getName());
- example4: 迫切加载的对象也会被缓存
// 加载学生,会立即加载班级
Student student = session.get(Student.class, 1L);
System.out.println(student.getName());
// 获得session缓存中的数据
Set keys = session.getStatistics().getEntityKeys();
System.out.println(keys);
// 不发SQL:使用缓存
Clazz clazz2 = session.load(Clazz.class, 2L);
System.out.println(clazz2.getName());
二级缓存(sessionFactory级缓存)
ehcache基本知识
-
二级缓存常用ehcache缓存,是一个线程级缓存,即运行在JVM中
-
持久化状态对象,会放入二级缓存
-
get()和load()会先使用session缓存,再使用二级缓存,如果缓存没有则查询数据库
ehcache开启步骤
-
放入ehcache的jar包
-
编写ehcache.xml配置文件
-
在hibernate.cfg.xml中开启二级缓存
-
在实体类上或实体集合属性上使用@Cache注解
ehcache.xml配置文件
<?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="../ehcache/hibernate"/>
<defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="600" timeToLiveSeconds="3600"
overflowToDisk="true" diskSpoolBufferSizeMB="30" maxElementsOnDisk="10000000"
diskPersistent="false" diskExpiryThreadIntervalSeconds="600"/>
<!-- hibernate内置的缓存区域 -->
<cache name="org.hibernate.cache.internal.StandardQueryCache" maxElementsInMemory="50" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="7200" overflowToDisk="true"/>
<cache name="org.hibernate.cache.internal.UpdateTimestampsCache" maxElementsInMemory="5000" eternal="true" overflowToDisk="true"/>
<!-- 实体对象定制缓存,没定制的实体使用默认设置 对类使用-->
<cache name="com.bfs.entity.Student" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="600" timeToLiveSeconds="7200" overflowToDisk="true"/>
<cache name="com.bfs.entity.Clazz" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="600" timeToLiveSeconds="7200" overflowToDisk="true"/>
<!-- 实体对象定制缓存,没定制的实体使用默认设置 对集合属性使用-->
<cache name="com.bfs.entity.Clazz.students" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="600" timeToLiveSeconds="7200" overflowToDisk="true"/>
<!-- 手动使用缓存 -->
<cache name="mycache" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="600" timeToLiveSeconds="7200" overflowToDisk="true"/>
</ehcache>
hibernate.cfg.xml中开启二级缓存
<!-- 开启二级缓存 -->
<property name="cache.use_second_level_cache">true</property>
<!-- 开始查询缓存(开始二级缓存就开启查询缓存) -->
<property name="cache.use_query_cache">true</property>
<!-- 设置使用的二级缓存为ehcache -->
<property name="cache.region.factory_class">org.hibernate.cache.ehcache.internal.EhcacheRegionFactory</property>
在实体类上或实体集合属性上使用@Cache注解
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
- READ_WRITE: 缓存可读写
- 标注的类或者集合属性必须在XML中注册开启
- 实体类
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Clazz {
- 属性集合
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<Student> students = new HashSet<>();
查询缓存(属于二级缓存)
介绍
- 缓存查询语句和对应的结果集
- key: 查询语句(条件参数不同,是不同的key)
- value:查询到的结果集(一般是集合)
- 开启查询缓存:
setCacheable(true)
- 先使用查询缓存(属于二级),如果没有则查询数据库,并将查询结果放入查询缓存
example
String hql = "from Clazz";
List<Clazz> list = session.createQuery(hql, Clazz.class).setCacheable(true).list();
System.out.println(list);
String hql2 = "select c from Clazz c";
List<Clazz> list2 = session.createQuery(hql2, Clazz.class).setCacheable(true).list();
System.out.println(list2);
缓存机制的其他问题
- 缓存的目的,查询时不用去数据库查询而直接从内存读入
- 缓存的数据和数据库的纪录必须保持一致,或者缓存不可用
一级缓存溢出
如果循环插入的数据量比较多时,需要及时清空session缓存
save(s)之后,s对象是持久状态的,它存储在session缓存中,session缓存的内存,可能内存溢出
for(int i=1; i<=1000000; i++) {
Student s = new Student();
s.setName("test_" + i);
session.saveOrUpdate(s);
// 如果循环插入的数据量比较多时,需要及时清空session缓存
// save(s)之后,s对象是持久状态的,它存储在session缓存中
// session缓存的内存,可能内存溢出
if(i%1000 == 0) {
session.flush(); //强制同步数据库
session.clear(); //清空session缓存,避免内存溢出
}
}
save()/update()/delete()只影响缓存中单个实体
- 一个session中,get()同一个ID时,不会去二级缓存查找
Clazz clazz = session.get(Clazz.class, 1L);
session.clear(); //清空session缓存,但不影响二级缓存
// 插入一条新记录
Clazz c = new Clazz();
c.setName("17-11");
session.saveOrUpdate(c); //此时,会发SQL(id是数据库自增)
session.clear();
// 一个session中,get()同一个ID时,不会去二级缓存查找
session = HibernateUtil.openSession();
clazz = session.get(Clazz.class, 1L);
- 验证
Session session = HibernateUtil.openSession();
Clazz clazz = session.get(Clazz.class, 1L);
System.out.println("=========================: " + clazz.getId());
HibernateUtil.closeSession();
// 更新一条记录
session = HibernateUtil.openSession();
Transaction tx = session.beginTransaction();
Clazz c2 = session.get(Clazz.class, 2L);
c2.setName("aaa");
session.saveOrUpdate(c2); //非必要
tx.commit(); //如果session中对象和数据库中对象不一致,则发SQL
HibernateUtil.closeSession();
// 二级缓存中数据有效:不发SQL
session = HibernateUtil.openSession();
clazz = session.get(Clazz.class, 1L); //二级缓存(Clazz#1有效)
System.out.println("=========================: " + clazz.getId());
HibernateUtil.closeSession();
update,delete语句进行批量操作会影响整个实体缓存
update, delete语句进行批量操作时,会缓存整个实体缓存(全部不可用)
Session session = HibernateUtil.openSession();
Clazz clazz = session.get(Clazz.class, 1L);
System.out.println("=========================: " + clazz.getId());
HibernateUtil.closeSession();
// 更新一条记录
session = HibernateUtil.openSession();
Transaction tx = session.beginTransaction();
String hql = "update Clazz set name=?1 where id=2"; //将Clazz相关缓存对象全部标识不可用
session.createQuery(hql).setParameter(1, "bbbb").executeUpdate();
tx.commit();
HibernateUtil.closeSession();
// 二级缓存中数据无效,会发SQL
session = HibernateUtil.openSession();
clazz = session.get(Clazz.class, 1L); //必须去数据库查询
System.out.println("=========================: " + clazz.getId());
HibernateUtil.closeSession();
Spring XML
面向对象设计原则
-
开闭原则(The Open-Closed Principle ,OCP)
- 对扩展开放
- 对修改关闭
-
里氏替换原则(Liskov Substitution Principle ,LSP)
- 子类应当可以替换基类并出现在基类能够出现的任何地方
- 子类可以扩展父类的功能,但不能改变父类原有的功能
-
迪米特原则(最少知道原则)(Law of Demeter ,LoD)
- 降低类之间的耦合,尽量减少对其他类的依赖
- 是否可以减少public方法和属性,是否可以修改为private等
-
单一职责原则
- 只能让一个类/接口/方法有且仅有一个职责
- 所谓一个类的一个职责是指引起该类变化的一个原因
-
接口分隔原则(Interface Segregation Principle ,ISP)
- 一个类对一个类的依赖应该建立在最小的接口上
- 建立单一接口,不要建立庞大臃肿的接口
- 尽量细化接口,接口中的方法尽量少
单一职责强调的是接口、类、方法的职责是单一的,强调职责
接口分隔原则主要是约束接口,针对抽象、整体框架 -
依赖倒置原则(Dependency Inversion Principle ,DIP)
- 高层模块不应该依赖于低层模块,二者都应该依赖于抽象
- 抽象不应该依赖于细节,细节应该依赖于抽象
- 针对接口编程,不要针对实现编程
-
组合/聚合复用原则(Composite/Aggregate Reuse Principle ,CARP)
- 尽量使用组合/聚合,不要使用类继承。
-
关联关系与依赖关系区分
- 关联关系:一般关联、组合、聚合, has a
class A {
private B b; //关联
}
- 依赖关系
class A {
public void test(B b) {}
}
- 继承关系 is a
控制反转IoC和依赖注入DI
-
IoC和DI是在不同的角度讲同一件事件,一般使用IoC
-
IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。
-
依赖注入的三种方式:
- 构造器注入
- setter方法注入
- 注解注入
- 依据现有bean注入
- 只有spring容器创建的, 才会依赖注入, new的不会注入
UserService userService = new UserService();
// userService对象不是spring容器创建的, 那么无法依赖注入
System.out.println(userService.getUserDao());
ApplicationContext atx = new ClassPathXmlApplicationContext("beans2.xml");
UserService userService2 = atx.getBean(UserService.class);
// userService2对象是spring容器创建的, 会依赖注入
System.out.println(userService2.getUserDao());
Spring环境搭建
-
加入Spring框架相关的jar包
-
编写Spring的配置文件
- xml方式
- 注解方式
-
实例化ApplicationContext,从spring容器中获得对象
-
Bean的配置
Spring核心技术
- IoC:控制反转
- 依赖注入(DI):注入依赖对象(属性)
- 控制反转和依赖注入是在不同的角度讲同一件事件,一般使用控制反转
- AOP:面向切面编程
动态代理
Spring历史
spring 1.2
spring 2.0:xml配置
spring 2.5:引入注解配置
spring 3.x:mvc的改进
spring 5.x:新特性
Spring XMl配置文件
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd">
<!-- bean1 -->
<bean id="userService" class="com.bfs.serive.UserService"/>
<!-- bean2 -->
<bean id="userService" class="com.kzw.service.UserService">
<!-- 注入依赖对象 -->
<property name="dao" ref="userDao"/>
</bean>
<!-- bean3 -->
<bean id="ctime" class="java.util.Date" scope="prototype" primary="true"/>
<!-- bean4 -->
<bean id="df" class="java.text.SimpleDateFormat">
<constructor-arg value="yyyy-MM-dd HH:mm:ss"/>
</bean>
<bean id="date" class="java.util.Date" scope="prototype"/>
<bean id="str" factory-bean="df" factory-method="format">
<constructor-arg ref="date"/>
</bean>
</beans>
XML配置介绍
bean的定义
<!-- 对类注入 -->
<bean id="userDao" class="com.kzw.dao.impl.UserDaoImpl"/>
-
Spring容器,是一个map。map_key是bean的ID,map_value是根据class生成的对象
默认是根据无参构造器new出来的,所以一般需要提供无参的构造方法
-
相当于
new XX()
,class必须是具体的实现类,一般要求有无参构造方法
scope作用域
<!-- Date ctime = new Date() -->
<!-- scope="prototype" 每次返回不同对象 -->
<bean id="ctime" class="java.util.Date" scope="prototype" primary="true"/>
-
scope="singleton"
:单例模式,默认值,每次返回同一个对象 -
scope="prototype"
:原型模式,每次返回一个不同的对象,struts2中的action必须使用该方式 -
scope="request/session"
:不常用,用于web项目中
value与ref
- value:指传入一个字符串型的值(使用属性编辑器进行类型转换)
- ref:指定引用的另一个bean的id
<property name="xx" value="字符串值"/>
-
<property name="xx" ref="引用bean的id"/>
或者:
<property name="xx">
<ref bean="引用bean的id"/>
</property>
- 使用内嵌bean:
<property name="xx">
<bean class="类型"/>
</property>
依赖对象的注入
<bean id="userService" class="com.kzw.service.UserService">
<!-- 对类的属性注入依赖对象 -->
<property name="dao" ref="userDao"/>
</bean>
-
相当于
setXx()
-
注入方式
- setter注入,对于属性
- 构造器注入,对于类
-
依据现有bean注入,对于类
- 注解
-
注入形式
- ref: 通过ID引用另一个bean
- value: 设置一个值(字符串或数值等)
带参构造器注入
<!--1-->
<!--
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
String str = df.format(date);
-->
<!-- 带参构造器注入 -->
<bean id="df" class="java.text.SimpleDateFormat">
<constructor-arg value="yyyy-MM-dd HH:mm:ss"/>
</bean>
<!-- 无参构造器注入 -->
<bean id="date" class="java.util.Date" scope="prototype"/>
<!--2-->
<!-- 通过构造器方式注入:调用某带参的构造方法 -->
<bean id="user2" class="com.kzw.bean.User" primary="true">
<constructor-arg value="100"/>
<constructor-arg value="李四"/>
</bean>
依据现有bean普通方法注入
- xml1
<!-- String str = df.format(date); -->
<bean id="df" class="java.text.SimpleDateFormat">
<constructor-arg value="yyyy-MM-dd HH:mm:ss"/>
</bean>
<bean id="date" class="java.util.Date" scope="prototype"/>
<bean id="str" factory-bean="df" factory-method="format">
<constructor-arg ref="date"/>
</bean>
- xml2
<!-- 使用普通方法返回对象(不带参数) -->
<bean id="factory" class="com.kzw.factory.Factory2"/>
<bean id="user5" factory-bean="factory"
factory-method="getInstanse"/>
<!-- 使用普通方法返回对象(带参数) -->
<bean id="user6" factory-bean="factory"
factory-method="getInstanse">
<constructor-arg value="200"/>
<constructor-arg value="李四22"/>
</bean>
- java
public class Factory2 {
public User getInstanse() {
return new User(2L, "李四");
}
public User getInstanse(Long id, String name) {
return new User(id, name);
}
}
使用静态方法注入对象
- 在无现有bean情况下调动方法注入,注意要为static方法
- xml
<!-- 使用静态方法返回对象(不带参数) -->
<bean id="user3" class="com.kzw.factory.Factory1"
factory-method="getInstanse"/>
<!-- 使用静态方法返回对象(带参数) -->
<bean id="user4" class="com.kzw.factory.Factory1"
factory-method="getInstanse">
<constructor-arg value="100"/>
<constructor-arg value="张三11"/>
</bean>
- java
public class Factory1 {
public static User getInstanse() {
return new User(1L, "张三");
}
public static User getInstanse(Long id, String name) {
return new User(id, name);
}
}
内嵌bean注入
<bean id="d" class="java.util.Date" scope="prototype"/>
<bean id="df" class="java.text.SimpleDateFormat">
<constructor-arg value="yyyy-MM-dd HH:mm:ss"/>
</bean>
<bean id="str" factory-bean="df" factory-method="format" scope="prototype">
<constructor-arg ref="d"/>
</bean>
<!-- 内嵌bean -->
<bean id="str2" factory-bean="df" factory-method="format" scope="prototype">
<constructor-arg>
<!-- 定义局部bean,不需要配置id -->
<bean class="java.util.Date"/>
</constructor-arg>
</bean>
集合属性注入
- xml
<!-- 集合属性的注入 -->
<bean id="mybean" class="com.kzw.bean.MyBean">
<property name="array">
<array>
<value>111</value>
<value>111</value>
<value>111</value>
</array>
</property>
<property name="list">
<list>
<value>aaa</value>
<value>aaa</value>
<value>aaa</value>
</list>
</property>
<property name="set">
<list>
<value>aaa</value>
<value>aaa</value>
<value>aaa</value>
</list>
</property>
<property name="map">
<map>
<entry key="id" value="100"/>
<entry key="name" value="张三"/>
<entry key="birthday" value-ref="date"/>
</map>
</property>
<property name="props">
<props>
<prop key="id">200</prop>
<prop key="name">李四</prop>
<prop key="age">30</prop>
</props>
</property>
<property name="props2">
<value>
id=300
name=\u738b\u4e94
age=30
</value>
</property>
<property name="cls" value="com.kzw.bean.User"/>
</bean>
- java
public class MyBean {
private String[] array;
private List<String> list;
private Set<String> set;
private Map<String, Object> map;
private Properties props;
private Properties props2;
private Class<?> cls;
bean的生命周期
-
init-method="方法名":在bean实例化并依赖注入完成之后,调用该方法
-
destroy-method="方法名":在spring容器关闭之前,调用该方法
-
使用注解
- xml文件
<!-- 通过setter方式注入: 要求有无参构造方法 -->
<bean id="user1" class="com.kzw.bean.User" init-method="init" destroy-method="close" lazy-init="true">
<property name="id" value="1" />
<property name="name" value="张三" />
</bean>
- java文件
private Long id;
private String name;
private Date ctime;
public void init() {
System.out.println("初始化:" + id + ", " + name);
}
public void close() {
System.out.println("销毁:" + id);
}
属性编辑器
作用
将字符串类型转换成属性需要的类型
<!-- 识别value -->
<property name="age" value="20"/>
<property name="date" value="2020-04-06 10:20"/>
<!-- value="1,张三,20" ==> User(id=1, name="张三", age=30) -->
<property name="card" value="200,1122,北京"/>
步骤
- 编写属性编辑器
- 注册属性编辑器
属性编辑器方式1--PropertyEditorSupport
- XML文件
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<!-- 对注册器对象的属性进行map类型注入 -->
<property name="customEditors">
<map>
<entry key="java.util.Date"
value="com.kzw.editor.MyDateEditor" />
<entry key="com.kzw.bean.Card"
value="com.kzw.editor.MyCardEditor"/>
</map>
</property>
</bean>
- MyDateEditor文件
public class MyDateEditor extends PropertyEditorSupport {
/**
* 处理字符串,并包装成需要的某类型对象
*/
@Override
public void setAsText(String text) throws
IllegalArgumentException {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm");
try {
Date date = df.parse(text);
// 包装成对象,并进行赋值, 然后spring将此对象注入
this.setValue(date);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
- MyCardEditor文件
public class MyCardEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws
IllegalArgumentException {
String[] arr = text.split(",");
Card c = new Card();
c.setId(Long.parseLong(arr[0]));
c.setSno(arr[1]);
c.setAddr(arr[2]);
setValue(c);
}
}
属性编辑器方式2--PropertyEditorRegistrar
- XML文件
<!-- 注册属性编辑器 -->
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<array>
<bean class="com.kzw.editor.MyEditorRegistrar">
<!-- 对注册器对象的属性进行注入 -->
<property name="format"
value="yyyy-MM-dd HH:mm"/>
</bean>
</array>
</property>
</bean>
- MyEditorRegistrar文件
public class MyEditorRegistrar implements PropertyEditorRegistrar {
private String format = "yyyy-MM-dd HH:mm:ss";
@Override
public void registerCustomEditors(
PropertyEditorRegistry registry) {
// 注册Date类型的属性编辑器
DateFormat df = new SimpleDateFormat(format);
registry.registerCustomEditor(
Date.class, new CustomDateEditor(df, true));
// 注册Card类型的属性编辑器
registry.registerCustomEditor(
Card.class, new MyCardEditor());
}
public void setFormat(String format) {
this.format = format;
}
}
获取bean
- 初始化Spring容器
ApplicationContext atx = new ClassPathXmlApplicationContext("xx.xml");
- 获得bean
atx.getBean(UserService.class);
atx.getBean("userService", UserService.class);
// 初始化Spring容器
ApplicationContext atx = new ClassPathXmlApplicationContext("beans.xml");
// 根据类型获得bean
UserService service = atx.getBean(UserService.class);
service.save();// 调用方法
// 根据ID获得bean
UserService service2 = atx.getBean("userService", UserService.class);
service2.save();// 调用方法
使用ApplicationContext.getBean() 获取bean:
- 根据类型获得bean:
- 比较方便
- 如果同一个类型有多个bean定义时,会报错(可以指定primary="true")
2、根据id获得bean:
- id是唯一,所以能返回一个对象
- 如果声明转换类型不一致,会报错
Spring 注解
依赖注入注解
注意:需要开启依赖注入注解功能
<context:annotation-config/>
- @Value:注入字符串,使用属性编辑器进行类型转换
- @Autowired:spring提供,只根据类型进行注入(类型兼容,接口,继承等等)
- @Resource:jdk提供,先根据属性名在spring容器中根据id查找bean,如果没有则根据类型查找
- @Resource可能需要查2次- 根据属性名查找到的bean,如果bean的类型和属性的类型不一样,会报错
Bean定义注解
注意:需要开启组件自动扫描
<context:component-scan base-package="com.bfs" />
扫描指定路径下的所有类中的 @Component, @Repository, @Service, @Controller多个路径以逗号隔开,被扫描到的成为Spring中的Bean,如果里面有依赖注入注解,便会进行依赖注入
- @Repository:通常用在DAO类上
- @Service:通常用于Service类上
- @Controller:通常用于Action类上
- @Component:用于其它情况
这四个注解,功能(定义Bean)是一样的。在spring mvc中,@Controller有特殊用途
- xml
<!-- 开启依赖注入的注解 -->
<context:annotation-config/>
<!-- 开启bean定义的注解:组件自动扫描-->
<context:component-scan base-package="com.kzw2" />
<!-- 注册属性编辑器 -->
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<array>
<bean class="com.kzw.editor.MyEditorRegistrar">
<property name="format" value="yyyy-MM-dd HH:mm"/>
</bean>
</array>
</property>
</bean>
- java
@Component
public class User {
@Value("1")
private Long id;
@Value("张三")
private String name;
@Value("2020-04-06 10:20")
private Date ctime;
@Autowired
private Card card;
Spring配置注解(完全使用注解)
Spring配置相关注解
- @Configuration:表示这是一个spring配置文件
- @ComponentScan:开启组件自动扫描(扫描4个注解),默认basePackages为当前类所在目录(即./*)
- @Bean:定义一个bean,bean的id为方法名称
注意:@Bean定义的方法,在Spring容器初始化调用的 - @Scope:bean默认是单例模式,指定原型模式 @Scope("prototype")
- @Qualifier:微调器,根据id指定一个bean
- @DependsOn:该bean定义要依赖于另一个bean的定义
- @PostConstruct:该对象创建完成之后调用,相当于 init-method
- @PreDestroy:该对象销毁时调用,相当于 destroy-method.
- @Primary: 根据类型查找时,优先级最高
- @PropertySource("classpath:/jdbc.properties") :加载属性文件
依赖注入方式
方法的参数,会自动依赖注入
注意: 根据参数注入,若是根据类型寻找的,当有多个的时候,如果没有@Primary会报错
- 根据类型
@Bean
@Scope("prototype")
public Date ctime(){
return new Date();
}
@Bean
@Scope("prototype")
@Primary
public Date ctime2(){
return new Date();
}
@Bean
public User user2(Date date){
- 根据id
// 根据id注入bean
@Bean
public String bean2(@Qualifier("user2") User user) {
System.out.println("bean2: " + user);
return "bean2";
}
调用另一个标注了@Bean的方法
@Bean // bean的id=user1
public User user1() {xxx;}
User user1 = user1(); // 依赖注入
注解形式的配置文件
基础配置和依赖注入
/**
* Spring的配置文件(注解方式)
*/
@Configuration
@ComponentScan("com.kzw")
public class AppConfig {
@Autowired
private UserService userService;
@Bean // bean的id=user1
@Primary // 根据类型查找时,优先级最高
public User user1() {
System.out.println("初始化:user1");
User u = new User(2L, "李四");
u.setCtime(new Date());
userService.save(u);
return u;// 注意要返回
}
@Bean
@Scope("prototype")
public Date ctime() {
System.out.println("初始化:ctime");
return new Date();
}
// 依赖注入方式1:方法的参数,会自动依赖注入
@Bean
public User user2(Date date) {
System.out.println("初始化:user2");
User u = new User(3L, "王五");
u.setCtime(date);
return u;
}
// 依赖注入方式2:调用另一个标注了@Bean的方法
@Bean
public User user3() {
Date date1 = ctime(); // 依赖注入
Date date2 = ctime();
System.out.println(date1 == date2); // false,原型模式
User user1 = user1(); // 依赖注入
User user2 = user1();
System.out.println(user1 == user2); // true,单例模式
User u = new User(4L, "小刘");
u.setCtime(date1);
return u;
}
/**
* 使用参数方式注入一个UserService类型的bean
* 调用方法方式注入一个User类型对象
*/
@Bean
@DependsOn("bean2")
public String bean1(UserService userService) {
// 此外并不是调用方法,而是获得一个id=user3的bean
User user = user3();
System.out.println("bean1: " + user);
userService.save(user);
return "bean1";
}
// 根据id注入bean
@Bean
public String bean2(@Qualifier("user2") User user) {
System.out.println("bean2: " + user);
return "bean2";
}
}
其他注解
@Configuration
@PropertySource("classpath:/jdbc.properties") // 加载属性文件
public class AppConfig2 {
// 方式1:注入Environment对象
@Autowired
private Environment env;
@Bean
public String test1() {
String username = env.getProperty("jdbc.username");
System.out.println("username: " + username);
return "test1";
}
// 为了可以使用@Value("${xxx}")
@Bean
public static PropertySourcesPlaceholderConfigurer
placeholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
// 方式2:使用@Value("${xxx}"),但是必须加上的配置
@Value("${jdbc.password}")
private String password;
@Bean
public String test2() {
System.out.println("test2, password: " + password);
return "test2";
}
@Bean // 属性注解
public String test3(@Value("${jdbc.username}") String username)
{
System.out.println("test3, username: " + username);
return "test2";
}
}
注解配置的属性编辑器
FormattingConversionService+自带(默认)属性编辑器
- 此处bean的id=conversionService,不能随便写
- 该bean在spring内部通过bean的id查找
- 有多个属性编辑器,
conversionService.addFormatter
继续添加
/**
* 属性编辑器: 方式1
* 此处bean的id=conversionService,不能随便写
* 原因:在spring内部通过bean的id查找
*/
@Bean
public FormattingConversionService conversionService() {
FormattingConversionService conversionService =
new DefaultFormattingConversionService(false);
conversionService.addFormatter(
new DateFormatter("yyyy-MM-dd HH:mm"));
return conversionService;
}
CustomEditorConfigurer+自定义属性编辑器
- 此处bean的id可以随便写,但是方法必须为static
- 该bean在spring内部通过bean的类型查找
- 有多个属性编辑器,
map.put(目标类型.class,自定义属性编辑器.class);
继续添加
// 属性编辑器: 方式2
// 必须为static方法
@Bean
// 必须static
public static CustomEditorConfigurer editorConfigurer() {
Map<Class<?>, Class<? extends PropertyEditor>> map =
new HashMap<>();
//map.put(目标类型.class,自定义属性编辑器.class);
map.put(Date.class, MyDateEditor.class);
CustomEditorConfigurer cfg = new CustomEditorConfigurer();
cfg.setCustomEditors(map);
return cfg;
}
Spring aop
代理
静态代理:代理设计模式
代理类是自己定义的
动态代理:代理类在JVM中动态创建的
-
JDK动态代理:使用JDK中的Proxy类,动态创建代理对象(前提:目标对象类必须实现了业务接口), 优先使用JDK的动态代理
- 原理:根据目标类对象实现的接口,动态创建一个类也实现这些接口(目标对象类和代理对象类是兄弟关系,都实现相同接口)
-
CGLIB动态代理:第三方提供,动态创建代理对象。(目标对象类可以不实现任何接口)
- 原理:动态创建目标对象类的子类(目标对象类是代理对象类的父类)