JAVAEE

Hibernate入门简介

Hibernate环境搭建

  1. 加入hibernate框架相关的jar包(lib/required)
  2. 编写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&amp;serverTimezone=UTC&amp;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核心概念

常用类

  1. SessionFactory
  • 它表示一个数据库(连接信息)
  • 它是重要级对象,创建比较耗时
  • 线程安全的,可以全局共享(static)
  • 一般不需要关闭
  1. Session
  • 它表示一次连接(包装Connection对象)
  • 它是轻量级对象,用完需要关闭
  • 线程不安全的,但在线程内部应该尽可能共享(ThreadLocal)
  1. Transaction
  • 事务(事务提交、事务回滚)
  • 增删改操作,需要使用事务
  • 查询操作,可以不使用事务

SessionFactory创建方式

  1. hibernate3.x和hibernate5.x中支持
SessionFactory factory = new Configuration().configure().buildSessionFactory();
  1. hibernate4.x和hibernate5.x中支持
ServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build();
Metadata metadata = new MetadataSources(registry).buildMetadata();
SessionFactory factory = metadata.buildSessionFactory();

HibernateUtil工具类

  1. 初始化SessionFactory
  2. 尽可能复用Session
  3. 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

  1. @Table(name="xx"):指定映射的表名
  • 如果不指定默认表名为类名

  • 表名尽量不要有大写字母

  • oracle:不区分大小写(自动变成大写)

  • mysql:默认window不区分大小写,但linux区分大小写

  • 不可用'-'等等

  1. @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取值情况

  1. native: 等同于strategy=IDENTITY

  2. sequence:等同于@SequenceGenerator

@Entity
@Table(name = "my_user3")
@GenericGenerator(name="mynative", strategy="native") //也可以在其它类中使用
public class User3 {

	@Id
	@GeneratedValue(generator = "mynative")
	private Long id;
  1. 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;
  1. 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;
  1. assigned:不使用主键生成,需要手工赋值

  2. foreign:在一对一关联关系时,可能用到

uuid和guid的区别

  1. uuid: 由hibernate生成,有一定的顺序, 推荐使用
  2. guid: 由数据库生成,select uuid(),无序

属性使用基本类型还是包装类型

  1. 主键要使用包装类型:Long/Integer
  2. 普通属性,使用包装类型(Integer)总体要比基本类型(int)好
  • Integer:数据库字段可以为空

建议使用
查询条件:select * from tb where status is not null and status!=1

  • int:数据库字段不能为空(不能将null赋值给int)

int类型属性有默认值0
查询条件:不需要判断是否为空

主键使用整型还是字符串

  1. 主键自增(整型):
    • 优点:占空间小,性能好一点,有顺序,比较直观
    • 不足:分布式多库部署存在问题(合库非常麻烦)
  2. uuid(字符串型):
    • 优点:兼容所有数据库,适合分布式多库部署
    • 不足:相比整型而言,占空间大一点,性能差一点,无顺序,不直观

主键策略的选择

  1. mysql, sqlserver:

@GeneratedValue(strategy=GenerationType.IDENTITY)

  1. oracle:

@SequenceGenerator(name = "seq_user", sequenceName = "S_USER", allocationSize = 1)
@GeneratedValue(generator = "seq_user")
注意:一般每张表定义一个生成器

  1. 通用uuid:

@GenericGenerator(name="myuuid", strategy="uuid")
@GeneratedValue(generator = "myuuid")
注意:uuid可以实体间共享

Hibernate关联关系映射

关联关系

  • 一对多:一个班有多个学生
  • 多对一:一个学生只属于一个班级
  • 一对一:一个人只有一张身份证(一张身份证只属于一个人)
  • 多对多:一个学生选择多门课程,一门课程有多个学生(数据库中要拆分成2个多对一)

单向关联关系和双向关联关系

  • 双向关系:通常使用双向关联(关系不紧密时,可使用单向关系)
  • 单向关系
    • 优先使用单向的多对一
    • 单向的一对多: 性能较差,由“一”的一方来维护双方关系

多向一对多

  1. @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<>();
  1. @ManyToOne @JoinColumn(name = "clazz_id")
  • @ManyToOne:many是自身类(学生),one是属性(clazz)

  • @JoinColumn(name = "clazz_id"):自身类(学生)加入一列(id) 来代替clazz对象的存储

  1. 级联关系 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")
  1. 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;
}
  1. 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;
}
  1. 测试文件
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
  1. 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;
}
  1. 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;
}

多对多

生成中间表

  1. 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<>();
}
  1. 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<>();
}
  1. 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个一对多(手动中间表)

  1. 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<>();
}
  1. 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<>();
}
  1. 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增删改查

分类

  1. 增加:save(), saveOrUpdate()

  2. 修改:update(), saveOrUpdate()

  3. 删除:delete() //按ID删除

  4. 查询:

    • 按ID查询:get()/load()
    • HQL查询(JPQL):推荐使用(类似SQL)
    • 条件查询:不推荐使用(特殊繁琐)
    • 原生SQL查询:特殊场合下使用

HQL

HQL查询语言

HQL类似于SQL,但完全不同,HQL是面向对象的

  1. 查询结果集

  2. DML风格查询:

    增加: insert into tb(x, y) select xx, yy from tb2

    修改: update tb set x=xx

    删除: delete from tb

HQL语句的大小写敏感问题

  1. 类名和属性名:大小写敏感,如: Student

  2. 其它:大小写不敏感,如: select, SELECT, Select, SElect

HQL占位符

  1. 位置占位符: ?1,?2,...
  • setParameter(1, 25)
  1. 命名占位符: :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
  1. 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();
  1. 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();
  1. 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();
  1. 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连接查询

  1. 逗号关联(存在笛卡尔积)
// 存在笛卡尔积
// String hql = "from Student, Clazz";
String hql = "select s, c from Student s, Clazz c";

List<Object[]> list = session.createQuery(hql, Object[].class).list();
  1. 逗号关联(解决笛卡尔积)
// 模仿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();
  1. 对象隐式关联
// 隐式的关联:某班级中年龄<=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();
  1. 对象显式关联
// 显式的关联:连接查询
// 内连接: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();
  1. 内连接
// 连接查询(内连接)
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));

查询函数(日期)

  1. 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();
  1. 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):转成字符串
  1. 集合处理表达式
  • 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(集合)
  1. case表达式

    CASE {operand} WHEN {test_value} THEN {match_result} ELSE {miss_result} EN

  2. 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:在分组之后,过滤条件(可以使用聚合函数)
  1. 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();
  1. 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();
  1. 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();

对象的生命周期

生命周期状态

  1. 瞬时状态(Transient):使用new操作符得到的对象,没有和数据库表进行关联。(数据库中没有与之对应的纪录)
  2. 持久状态(Persist):持久对象是任何具有数据库标识的实例,它由Session统一管理。它们的状态在事务结束时同数据库进行同步。(数据库中有与之对应的纪录,并受session管理) 对持久状态对象的修改,会自动同步到数据库, 同步的同时(set方法)不发sql,在commit()/flush()时候统一发sql
  3. 脱管状态(Detached):Session关闭或调用clear()或evict(),不受Session的管理。(数据库中有与之对应的纪录,但不受session管理)

JAVAEE

@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();
}

函数

  1. 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();
  1. 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();
  1. 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语句

  1. 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
  1. 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
  1. 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
  1. 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()

  1. get()
  • get():立即发SQL,返回Clazz类型对象

  • 如果数据库没有该记录,则返回null,用到该对象时抛NullPointerException

// get():立即发SQL,返回Clazz类型对象
// 如果数据库没有该记录,则返回null,用到该对象时抛NullPointerException
Clazz clazz = session.get(Clazz.class, 1L);
  1. 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缓存

  1. 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();
  1. example2: clear()影响缓存
// 清空session缓存
session.clear();

// 发SQL
Clazz clazz2 = session.load(Clazz.class, 1L);
System.out.println(clazz2.getName());
  1. 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());
  1. 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开启步骤

  1. 放入ehcache的jar包

  2. 编写ehcache.xml配置文件

  3. 在hibernate.cfg.xml中开启二级缓存

  4. 在实体类上或实体集合属性上使用@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中注册开启
  1. 实体类
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Clazz {
  1. 属性集合
@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()只影响缓存中单个实体

  1. 一个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);
  1. 验证
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

面向对象设计原则

  1. 开闭原则(The Open-Closed Principle ,OCP)

    • 对扩展开放
    • 对修改关闭
  2. 里氏替换原则(Liskov Substitution Principle ,LSP)

    • 子类应当可以替换基类并出现在基类能够出现的任何地方
    • 子类可以扩展父类的功能,但不能改变父类原有的功能
  3. 迪米特原则(最少知道原则)(Law of Demeter ,LoD)

    • 降低类之间的耦合,尽量减少对其他类的依赖
    • 是否可以减少public方法和属性,是否可以修改为private等
  4. 单一职责原则

    • 只能让一个类/接口/方法有且仅有一个职责
    • 所谓一个类的一个职责是指引起该类变化的一个原因
  5. 接口分隔原则(Interface Segregation Principle ,ISP)

    • 一个类对一个类的依赖应该建立在最小的接口上
    • 建立单一接口,不要建立庞大臃肿的接口
    • 尽量细化接口,接口中的方法尽量少

    单一职责强调的是接口、类、方法的职责是单一的,强调职责
    接口分隔原则主要是约束接口,针对抽象、整体框架

  6. 依赖倒置原则(Dependency Inversion Principle ,DIP)

    • 高层模块不应该依赖于低层模块,二者都应该依赖于抽象
    • 抽象不应该依赖于细节,细节应该依赖于抽象
    • 针对接口编程,不要针对实现编程
  7. 组合/聚合复用原则(Composite/Aggregate Reuse Principle ,CARP)

    • 尽量使用组合/聚合,不要使用类继承。
  8. 关联关系与依赖关系区分

  • 关联关系:一般关联、组合、聚合, has a
class A {
   private B b;  //关联
}
  • 依赖关系
class A {
   public void test(B b) {}
}
  • 继承关系 is a

控制反转IoC和依赖注入DI

  1. IoC和DI是在不同的角度讲同一件事件,一般使用IoC

  2. IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。

  3. 依赖注入的三种方式:

  • 构造器注入
  • setter方法注入
  • 注解注入
  • 依据现有bean注入
  1. 只有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环境搭建

  1. 加入Spring框架相关的jar包

  2. 编写Spring的配置文件

    • xml方式
    • 注解方式
  3. 实例化ApplicationContext,从spring容器中获得对象

  4. Bean的配置

Spring核心技术

  1. IoC:控制反转
  • 依赖注入(DI):注入依赖对象(属性)
  • 控制反转和依赖注入是在不同的角度讲同一件事件,一般使用控制反转
  1. 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
  1. <property name="xx" value="字符串值"/>
  2. <property name="xx" ref="引用bean的id"/>
    或者:
<property name="xx">
	<ref bean="引用bean的id"/>
</property>
  1. 使用内嵌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普通方法注入

  1. 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>	
  1. 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>
  1. 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方法
  1. 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>
  1. 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>

集合属性注入

  1. 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>
  1. 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容器关闭之前,调用该方法

  • 使用注解

  1. 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>
  1. 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. 编写属性编辑器
  2. 注册属性编辑器

属性编辑器方式1--PropertyEditorSupport

  1. 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>    
  1. 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();
		}
	}

}

  1. 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

  1. 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>
  1. 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:

  1. 根据类型获得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有特殊用途
  1. 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>
  1. 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会报错

  1. 根据类型
@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){
  1. 根据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

代理

静态代理:代理设计模式

JAVAEE

代理类是自己定义的

动态代理:代理类在JVM中动态创建的

  • JDK动态代理:使用JDK中的Proxy类,动态创建代理对象(前提:目标对象类必须实现了业务接口), 优先使用JDK的动态代理

    • 原理:根据目标类对象实现的接口,动态创建一个类也实现这些接口(目标对象类和代理对象类是兄弟关系,都实现相同接口)
  • CGLIB动态代理:第三方提供,动态创建代理对象。(目标对象类可以不实现任何接口

    • 原理:动态创建目标对象类的子类(目标对象类是代理对象类的父类)
上一篇:Java常用设计模式—工厂模式


下一篇:SpringBoot+MQ+Redis+BootStrap 秒杀系统(一) 项目框架搭建