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"
        "//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管理)

生命周期

@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="//www.w3.org/2001/XMLSchema-instance" 
	xsi:noNamespaceSchemaLocation="//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="//www.springframework.org/schema/beans"
    xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="//www.springframework.org/schema/beans
    //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

代理

靜態代理:代理設計模式

proxy

代理類是自己定義的

動態代理:代理類在JVM中動態創建的

  • JDK動態代理:使用JDK中的Proxy類,動態創建代理對象(前提:目標對象類必須實現了業務接口), 優先使用JDK的動態代理

    • 原理:根據目標類對象實現的接口,動態創建一個類也實現這些接口(目標對象類和代理對象類是兄弟關係,都實現相同接口)
  • CGLIB動態代理:第三方提供,動態創建代理對象。(目標對象類可以不實現任何接口

    • 原理:動態創建目標對象類的子類(目標對象類是代理對象類的父類)
Tags: