JAVAEE
Hibernate入門簡介
Hibernate環境搭建
- 加入hibernate框架相關的jar包(lib/required)
- 編寫hibernate的配置文件:默認文件名稱 hibernate.cfg.xml
- 創建src/hibernate.cfg.xml(hibernate.properties)
- xml文件編寫規則(.dtd或者.xsd),使用dtd文件
- 如果xml文件中不能智慧提示,則需要配置dtd文件
- 配置<session-factory>節點:如資料庫連接資訊
配置文件
- 採用mysql-connector-java-8.0.13.jar,xml無問題,可能是此包版本問題
<!-- 採用mysql-connector-java-8.0.13 -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"//www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<!-- session-factory代表一個資料庫:配置property和mapping -->
<session-factory>
<!-- 方言:指定hibernate要生成何種資料庫的SQL語句 -->
<property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>
<!-- 資料庫連接 -->
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false</property>
<property name="connection.username">root</property>
<property name="connection.password">密碼</property>
<!-- 列印SQL語句 -->
<property name="show_sql">true</property>
<!-- 自動生成表結構 -->
<property name="hbm2ddl.auto">update</property>
<!-- mapping映射(xml或註解在哪) -->
<mapping class="com.bfs.entity.User"/>
</session-factory>
</hibernate-configuration>
編寫實體類(不需要建表)
- 在類上使用註解:@Entity, @Table
- 在屬性上使用註解:@Column
@Entity
@Table(name = "tb_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 自增
private Long id;
// String類型映射到資料庫欄位的長度,默認是255
@Column(length = 50)
private String name;
private int age;
// 省略setter、getter方法
}
測試
- 增加一條記錄
- 按主鍵查詢
public class Test3 {
@Test
public void test1() {
Session s = HibernateUtil.openSession();
Transaction tx = s.beginTransaction();
try {
User u = new User();
u.setName("李四");
u.setAge(20);
s.saveOrUpdate(u);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
HibernateUtil.closeSession();
}
}
@Test
public void query1() {
Session s = HibernateUtil.openSession();
String hql = "from User where age>=?1";
List<User> list = s.createQuery(hql, User.class).setParameter(1, 20).list();
for(User u : list) {
System.out.println(u);
}
HibernateUtil.closeSession();
}
@Test
public void query2() {
Session s = HibernateUtil.openSession();
String sql = "select * from tb_user where age>=?0";
List<User> list = s.createNativeQuery(sql, User.class).setParameter(0, 20).list();
for(User u : list) {
System.out.println(u);
}
HibernateUtil.closeSession();
}
@Test
public void query3() {
Session s = HibernateUtil.openSession();
CriteriaBuilder cb = s.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> root = query.from(User.class);
query.select(root);
query.where(cb.ge(root.get("age"), 20));
List<User> list = s.createQuery(query).list();
for(User u : list) {
System.out.println(u);
}
HibernateUtil.closeSession();
}
@AfterClass
public static void destory() {
HibernateUtil.destory();
}
}
Hibernate核心概念
常用類
- SessionFactory
- 它表示一個資料庫(連接資訊)
- 它是重要級對象,創建比較耗時
- 執行緒安全的,可以全局共享(static)
- 一般不需要關閉
- Session
- 它表示一次連接(包裝Connection對象)
- 它是輕量級對象,用完需要關閉
- 執行緒不安全的,但在執行緒內部應該儘可能共享(ThreadLocal)
- Transaction
- 事務(事務提交、事務回滾)
- 增刪改操作,需要使用事務
- 查詢操作,可以不使用事務
SessionFactory創建方式
- hibernate3.x和hibernate5.x中支援
SessionFactory factory = new Configuration().configure().buildSessionFactory();
- hibernate4.x和hibernate5.x中支援
ServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build();
Metadata metadata = new MetadataSources(registry).buildMetadata();
SessionFactory factory = metadata.buildSessionFactory();
HibernateUtil工具類
- 初始化SessionFactory
- 儘可能復用Session
- ThreadLocal的用法
public class HibernateUtil {
private static SessionFactory factory;
static {
factory = new Configuration().configure().buildSessionFactory();
}
// 執行緒局部變數
private static ThreadLocal<Session> session = new ThreadLocal<>();
/**
* 打開Session:執行緒不安全的,執行緒內部儘可能共享(執行緒變數)
* 1) 先從執行緒變數中獲得
* 2) 如果沒有再從factory中獲得,並放在執行緒變數中
* */
public static Session openSession() {
Session s = session.get();
if(s==null || !s.isOpen()) {
s = factory.openSession();
session.set(s);
}
return s;
}
/**
* 關閉Session
* */
public static void closeSession() {
Session s = session.get();
session.set(null);
if(s!=null && s.isOpen()) {
s.close();
}
}
/**
* 關閉SessionFactory
* */
public static void destory() {
if(factory.isOpen()) {
factory.close();
}
}
}
基本註解
@Table
- @Table(name=”xx”):指定映射的表名
-
如果不指定默認表名為類名
-
表名盡量不要有大寫字母
-
oracle:不區分大小寫(自動變成大寫)
-
mysql:默認window不區分大小寫,但linux區分大小寫
-
不可用’-‘等等
- @Table(indexes={@Index(columnList=”name,age”)}) 執行索引
- 索引:對查詢性能影響很大
- 備註:通常直接在資料庫表中增加索引
@Entity
表明實體
@Entity
@Table(name = "my_user", indexes = { @Index(columnList = "name"), @Index(columnList = "age") })
public class User {
@Column
默認所有屬性上都有@Column,即所有屬性都對應到資料庫的某個欄位
-
@Column(name=””):映射到資料庫的欄位名稱
-
@Column(length=255):資料庫欄位的字元長度(字元串類型默認長度為255)
-
@Column(nullable=false, unique=true):非空約束,唯一性約束(會增加索引)
// unique唯一性約束, 會增加索引 @Column(length=255, nullable=false, unique=false) private String name;
@Transient
表示屬性,但是不映射到資料庫的欄位
// 臨時屬性,資料庫不存儲該值
@Transient
private int myage;
主鍵生成策略
@Id
指定某屬性是主鍵,一般使用String或Long, Integer類型
主鍵生成策略默認assigned(手工賦值)
@GeneratedValue生成策略
- 如果不指定生成策略,則使用AUTO
- 使用JPA自帶的生成策略:strategy=共有4種取值
- IDENTITY:自增,最常用(mysql, sqlserver等)
- AUTO:等同SEQUENCE(如果資料庫不支援序列,則通過表模擬),oracle中,使用自定義sequence生成策略
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
自定義(hibernate提供)生成策略
@SequenceGenerator+@GeneratedValue
/**
* oracle資料庫表進行映射:
* 主鍵ID自增:使用sequence,並且每張表一個sequence
* 使用自定義生成器@SequenceGenerator
*/
@Entity
@Table(name = "my_user2")
@SequenceGenerator(name = "seq_user", sequenceName = "S_USER", allocationSize = 1) // 定義生成器
public class User2 {
@Id
@GeneratedValue(generator = "seq_user")
private Long id;
@GenericGenerator+@GeneratedValue
strategy取值情況
-
native: 等同於strategy=IDENTITY
-
sequence:等同於@SequenceGenerator
@Entity
@Table(name = "my_user3")
@GenericGenerator(name="mynative", strategy="native") //也可以在其它類中使用
public class User3 {
@Id
@GeneratedValue(generator = "mynative")
private Long id;
- uuid:主鍵是字元串,使用hibernate生成uuid
@Entity
@Table(name = "my_user4")
@GenericGenerator(name="myuuid", strategy="uuid") //也可以在其它類中使用
public class User4 {
@Id
@GeneratedValue(generator = "myuuid")
@Column(length = 36)
private String id;
- guid:主鍵是字元串,使用資料庫生成uuid(select uuid())
@Entity
@Table(name = "my_user5")
@GenericGenerator(name="myguid", strategy="guid")
public class User5 {
@Id
@GeneratedValue(generator = "myguid") //可以使用其它類定義的生成器
@Column(length = 36)
private String id;
-
assigned:不使用主鍵生成,需要手工賦值
-
foreign:在一對一關聯關係時,可能用到
uuid和guid的區別
- uuid: 由hibernate生成,有一定的順序, 推薦使用
- guid: 由資料庫生成,select uuid(),無序
屬性使用基本類型還是包裝類型
- 主鍵要使用包裝類型:Long/Integer
- 普通屬性,使用包裝類型(Integer)總體要比基本類型(int)好
- Integer:資料庫欄位可以為空
建議使用
查詢條件:select * from tb where status is not null and status!=1
- int:資料庫欄位不能為空(不能將null賦值給int)
int類型屬性有默認值0
查詢條件:不需要判斷是否為空
主鍵使用整型還是字元串
- 主鍵自增(整型):
- 優點:占空間小,性能好一點,有順序,比較直觀
- 不足:分散式多庫部署存在問題(合庫非常麻煩)
- uuid(字元串型):
- 優點:兼容所有資料庫,適合分散式多庫部署
- 不足:相比整型而言,占空間大一點,性能差一點,無順序,不直觀
主鍵策略的選擇
- mysql, sqlserver:
@GeneratedValue(strategy=GenerationType.IDENTITY)
- oracle:
@SequenceGenerator(name = “seq_user”, sequenceName = “S_USER”, allocationSize = 1)
@GeneratedValue(generator = “seq_user”)
注意:一般每張表定義一個生成器
- 通用uuid:
@GenericGenerator(name=”myuuid”, strategy=”uuid”)
@GeneratedValue(generator = “myuuid”)
注意:uuid可以實體間共享
Hibernate關聯關係映射
關聯關係
- 一對多:一個班有多個學生
- 多對一:一個學生只屬於一個班級
- 一對一:一個人只有一張身份證(一張身份證只屬於一個人)
- 多對多:一個學生選擇多門課程,一門課程有多個學生(資料庫中要拆分成2個多對一)
單向關聯關係和雙向關聯關係
- 雙向關係:通常使用雙向關聯(關係不緊密時,可使用單向關係)
- 單向關係:
- 優先使用單向的多對一
- 單向的一對多: 性能較差,由「一」的一方來維護雙方關係
多向一對多
- @OneToMany(mappedBy=”clazz”)
-
one:指自身類(本類),many:指屬性,前面是自身類,後面的是所標註的屬性
-
mappedBy=”屬性類的屬性名”:在自身類(班級)中放棄維護與被標註屬性(學生)的關係,參照屬性類(學生)中的clazz屬性,one放棄維護與many的關係
-
在c.students.add(學生)的時候不發SQL。因為設置mappedBy=”xx”,自身類不維護關聯關係
// one指自身(本類) many指屬性, 前面是自身 後面的是屬性
// 在班級中放棄維護與學生的關係,**參照學生中的clazz屬性**
@OneToMany(mappedBy="clazz")
private Set<Student> students = new HashSet<>();
- @ManyToOne @JoinColumn(name = “clazz_id”)
-
@ManyToOne:many是自身類(學生),one是屬性(clazz)
-
@JoinColumn(name = “clazz_id”):在自身類(學生)中加入一列(id) 來代替clazz對象的存儲
- 級聯關係 cascade
- 類型
- CascadeType.ALL
- CascadeType.PERSIST
- CascadeType.MERGE
- CascadeType.REMOVE 級聯刪除
- CascadeType.REFRESH
- CascadeType.DETACH
- 在@OneToMany @ManyToOne都要加上(多向一對多)
@OneToMany(mappedBy = "comment" ,cascade = CascadeType.REMOVE)
private Set<Reply> replys = new HashSet<>();
@ManyToOne(cascade = CascadeType.REMOVE)
@JoinColumn(name = "comment_id")
private Comment comment;
單向多對一
在Clazz類中不設置學生屬性(不維護學生屬性)
// Student類
@ManyToOne
@JoinColumn(name = "clazz_id")
private Clazz clazz;
單向一對多
// Clazz類
@OneToMany //維護關聯關係
@JoinColumn(name = "clazz_id")
private Set<Student> students = new HashSet<>();
一對一
基於外鍵的一對一
特殊的多對一,多方的外鍵增加唯一性約束
- 兩邊都使用@OneToOne,如果一邊沒有指定mappedBy=”xxx”
- 那麼相當於省略了:@JoinColumn(name=”xx”)
- Person
@Entity
@Table(name = "tb_person")
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 姓名
@Column(length = 50)
private String name;
// 擁有一張身份證
@OneToOne
@JoinColumn(name="card_id", unique=true)// tb_person多一列
private IdCard card;
}
- IdCard
@Entity
@Table(name = "tb_idcard")
public class IdCard {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 號碼
@Column(length = 20)
private String sno;
// 只屬於一個人
@OneToOne(mappedBy="card")
private Person person;
}
- 測試文件
public class T1 {
@Test
public void t1() {
Session session = HibernateUtil.openSession();
Transaction tx = session.beginTransaction();
Person p = new Person();
p.setName("張三");
IdCard card = new IdCard();
card.setSno("1234");
// 設置關聯關係
p.setCard(card); //有用
// 下面語句沒有作用,不發SQL。因為設置mappedBy="xx",不維護關聯關係
card.setPerson(p);
// 保存對象:注意順序
session.save(card);
session.save(p);
tx.commit();
HibernateUtil.closeSession();
}
@Test
public void t2() {
Session session = HibernateUtil.openSession();
String hql = "from Person";
List<Person> list = session.createQuery(hql, Person.class).list();
for(Person p : list) {
System.out.println(p.getName());
if(p.getCard() != null) {
System.out.println(p.getCard().getSno());
}
System.out.println("===========");
}
HibernateUtil.closeSession();
}
}
共享主鍵的一對一
- 主對象的ID自增,@PrimaryKeyJoinColumn
- 從對象的ID不能自增,需要參照主對象的ID
- Person
@Entity
@Table(name = "tb2_person")
public class Person2 {
// 主對象的ID自增
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 姓名
@Column(length = 50)
private String name;
// 擁有一張身份證
@OneToOne
@PrimaryKeyJoinColumn //默認是@JoinColumn
private IdCard card;
}
- IdCard
@Entity
@Table(name = "tb2_idcard")
public class IdCard2 {
// 從對象的ID不能自增,需要參照主對象的ID
@Id
@GeneratedValue(generator = "fk")
@GenericGenerator(
name="fk",
strategy="foreign",
parameters=@Parameter(name="property", value="person")
)
private Long id;
// 號碼
@Column(length = 20)
private String sno;
// 只屬於一個人
@OneToOne(mappedBy = "card")
private Person person;
}
多對多
生成中間表
- Student
@Entity
@Table(name = "tbx_student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 姓名
@Column(length = 50)
private String name;
// 有多門課
// 查詢學生的課程:根據學生ID去中間表,和student_id匹配,再用查詢到course_id和課程ID查詢
@ManyToMany
@JoinTable(name="tbx_student_course",
joinColumns= @JoinColumn(name="student_id"),
inverseJoinColumns=@JoinColumn(name="course_id"))
private Set<Course> courses = new HashSet<>();
}
- Course
@Entity
@Table(name = "tbx_course")
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 名稱
@Column(length = 50)
private String name;
// 有多個學生,放棄維護關聯關係(不發SQL)
@ManyToMany(mappedBy = "courses")
private Set<Student> studnets = new HashSet<>();
}
- test
// 設置關聯關係
s1.getCourses().add(c1); //有用
s1.getCourses().add(c2); //有用
s2.getCourses().add(c2); //有用
// 下面語句沒有作用,不發SQL。因為設置mappedBy="xx",不維護關聯關係
c1.getStudnets().add(s1);
c2.getStudnets().add(s2);
拆分2個一對多(手動中間表)
- Student
@Entity
@Table(name = "tbx2_student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 姓名
@Column(length = 50)
private String name;
// 有多個成績
@OneToMany(mappedBy = "student")
private Set<Score> cjs = new HashSet<>();
}
- Course
@Entity
@Table(name = "tbx2_course")
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 名稱
@Column(length = 50)
private String name;
/// 有多個成績
@OneToMany(mappedBy = "course")
private Set<Score> cjs = new HashSet<>();
}
- Score(中間表)
/**
* 成績表
*/
@Entity
@Table(name = "tbx2_score")
public class Score {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 屬於一個學生
@ManyToOne
@JoinColumn(name = "student_id")
private Student2 student;
// 屬於一門課程
@ManyToOne
@JoinColumn(name = "course_id")
private Course2 course;
// 分值
private int val;
}
Hibernate增刪改查
分類
-
增加:save(), saveOrUpdate()
-
修改:update(), saveOrUpdate()
-
刪除:delete() //按ID刪除
-
查詢:
- 按ID查詢:get()/load()
- HQL查詢(JPQL):推薦使用(類似SQL)
- 條件查詢:不推薦使用(特殊繁瑣)
- 原生SQL查詢:特殊場合下使用
HQL
HQL查詢語言
HQL類似於SQL,但完全不同,HQL是面向對象的
-
查詢結果集
-
DML風格查詢:
增加: insert into tb(x, y) select xx, yy from tb2
修改: update tb set x=xx
刪除: delete from tb
HQL語句的大小寫敏感問題
-
類名和屬性名:大小寫敏感,如: Student
-
其它:大小寫不敏感,如: select, SELECT, Select, SElect
HQL佔位符
- 位置佔位符:
?1,?2,...
- setParameter(1, 25)
- 命名佔位符:
:xxx
- setParameter(“age”, 25)
Session session = HibernateUtil.openSession();
//String hql = "from User where age>?1"; //位置佔位符
//String hql = "from User u where u.age>?1";
String hql = "select u from User u where u.age>?1";
List<User> list = session.createQuery(hql, User.class).setParameter(1, 25).list();
for(User u : list) {
System.out.println(u);
}
// 命名佔位符 :xx
String hql2 = "from User where age>:age and name like ?1";
List<User> list2 = session.createQuery(hql2, User.class)
.setParameter("age", 25)
.setParameter(1, "test%").list();
for(User u : list2) {
System.out.println(u);
}
HibernateUtil.closeSession();
增加 save(), saveOrUpdate()
save()
User u = new User();
u.setName("張三");
u.setAge(20);
session.save(u); //u.id的值由insert語句獲得的,發SQL語句
saveOrUpdate()
資料庫中存在有這個id的對象為更新,不存在為save
User4 u = new User4();
u.setName("張三");
u.setAge(20);
session.saveOrUpdate(u); //不發SQL,u.id的值由Hibernate生成
批量操作
for(int i=1; i<=20; i++) {
// 方式1:通過ID構造
//Clazz clazz = new Clazz();
//clazz.setId(i%2+1L);
// 方式2:從資料庫查詢
Clazz clazz = session.get(Clazz.class, i%2+1L);
// 學生
Student stu = new Student();
stu.setName("test_" + i);
stu.setAge(20 + i);
stu.setClazz(clazz); //保證clazz的id不為null
session.save(stu);
}
修改 update(), saveOrUpdate()
update()
Clazz c = session.get(Clazz.class, 1L);
c.setName("17-5");
session.update(c);
批量操作
Session session = HibernateUtil.openSession();
Transaction tx = session.beginTransaction();
// HQL的批量修改
String hql = "update Student set name=?1 where age>=?2";
session.createQuery(hql).setParameter(1, "測試").setParameter(2, 38).executeUpdate();
tx.commit();
HibernateUtil.closeSession();
刪除 delete()
通過ID構造對象進行刪除
Clazz c = new Clazz();
c.setId(1L);
session.delete(c);
查詢資料庫獲得對象
Clazz c = session.get(Clazz.class, 1L);
session.delete(c);
HQL語句(批量)刪除
Session session = HibernateUtil.openSession();
Transaction tx = session.beginTransaction();
// HQL的批量刪除
String hql = "delete from Student where age>=?1";
session.createQuery(hql).setParameter(1, 38).executeUpdate();
tx.commit();
HibernateUtil.closeSession();
查詢
原生SQL查詢
/**
* 原生SQL查詢
* */
@Test
public void query_sql() {
Session session = HibernateUtil.openSession();
// 位置佔位符和命名佔位符
String sql = "select * from tb_user where age>?1";
List<User> list = session.createNativeQuery(sql, User.class).setParameter(1, 25).list();
for(User u : list) {
System.out.println(u);
}
String sql2 = "select * from tb_user where age>:age";
List<User> list2 = session.createNativeQuery(sql2, User.class).setParameter("age", 28).list();
for(User u : list2) {
System.out.println(u);
}
HibernateUtil.closeSession();
}
Hibernate的條件查詢(已過時)
/**
* Hibernate的條件查詢(已過時)
* */
@Test
public void query_critera() {
Session session = HibernateUtil.openSession();
List<User> list = session.createCriteria(User.class).add(Restrictions.gt("age", 25)).list();
for(User u : list) {
System.out.println(u);
}
HibernateUtil.closeSession();
}
HQL單表查詢
- 創建查詢語句:
session.createQuery(hql, Student.class)
- 設置參數:
session.setParameter(1, 30)
- 得到查詢列表:
session.list()
- 結果數組:
session.uniqueResult()
: 得到 Object[] arr; - 按照ID查詢:
session.get(類型.class, 1L);
: 如果資料庫沒有該記錄,則返回null,用到該對象時拋NullPointerException
- List
//String hql = "from Student where age>=?1";
//String hql = "from Student s where s.age>=?1";
String hql = "select s from Student s where s.age>=?1";
List<Student> list = session.createQuery(hql, Student.class).setParameter(1, 30).list();
- List<Object[]>
// 查詢部分欄位:每條記錄是Object[]
String hql = "select id, name, age from Student where age>=?1";
List<Object[]> list = session.createQuery(hql, Object[].class).setParameter(1, 30).list();
- List
// 查詢部分欄位:每條記錄是 List
String hql = "select new list(id, name, age) from Student where age>=?1";
List<List> list = session.createQuery(hql, List.class).setParameter(1, 30).list();
- List
// 查詢部分欄位:每條記錄是 List
String hql = "select new map(id as id, name as name, age as age) from Student where age>=?1";
List<Map> list = session.createQuery(hql, Map.class).setParameter(1, 30).list();
HQL連接查詢
- 逗號關聯(存在笛卡爾積)
// 存在笛卡爾積
// String hql = "from Student, Clazz";
String hql = "select s, c from Student s, Clazz c";
List<Object[]> list = session.createQuery(hql, Object[].class).list();
- 逗號關聯(解決笛卡爾積)
// 模仿sql,解決笛卡爾積(糟糕方式)
String hql = "from Student s, Clazz c where s.clazz.id=c.id and c.name=?1 and s.age<?2";
List<Object[]> list = session.createQuery(hql, Object[].class).setParameter(1, "17-1班").setParameter(2, 30).list();
- 對象隱式關聯
// 隱式的關聯:某班級中年齡<=30
String hql = "from Student where clazz.name=?1 and age<?2";
List<Student> list = session.createQuery(hql, Student.class).setParameter(1, "17-1班").setParameter(2, 30).list();
- 對象顯式關聯
// 顯式的關聯:連接查詢
// 內連接:join, 外連接(左連接:left join、右連接:right join)
// sql的連接查詢:select x.*, y.* from tb_student x join tb_clazz on x.clazz_id=y.id where ...
String hql = "from Student s left join s.clazz c where c.name=?1 and s.age<=?2";
// form前省略:select s, c
List<Object[]> list = session.createQuery(hql, Object[].class).setParameter(1, "17-1班").setParameter(2, 30).list();
- 內連接
// 連接查詢(內連接)
String hql = "select s from Student s join s.clazz c where c.name=?1 and s.age<=?2";
List<Student> list = session.createQuery(hql, Student.class).setParameter(1, "17-1班").setParameter(2, 30).list();
HQL分頁和排序
setFirstResult(0)
//從第幾條記錄開始setMaxResults(5)
//每頁大小
// 排序和分頁
// 先按age降序排,如果age相同則再按name升序排
String hql = "from Student order by age desc, name asc";
List<Student> list = session.createQuery(hql, Student.class)
.setFirstResult(0) //從第幾條記錄開始, 從0開始算
.setMaxResults(5) //每頁大小
.list();
HQL迫切連接查詢
- join:連接查詢,返回Object[]
- join fetch:迫切連接查詢,返回單個對象,性能優化
// join:連接查詢,返回Object[]
// join fetch:迫切連接查詢,返回單個對象
// 迫切連接查詢: join fetch(性能優化)
String hql = "from Student s join fetch s.clazz c where c.name=?1 and s.age<=?2";
// 返回Student集合,並將clazz對象填充到student中
List<Student> list = session.createQuery(hql, Student.class).setParameter(1, "17-1班").setParameter(2, 30).list();
聚合查詢
一般與分組group by 一起使用
- count()
- avg()
- max()
- min()
- sum()
// 錯誤:String hql = "select * from Student";
String hql = "select count(*), min(age), max(age), avg(age), sum(age) from Student";
Object[] arr = session.createQuery(hql, Object[].class).uniqueResult();
System.out.println(Arrays.toString(arr));
查詢函數(日期)
- JPQL標準函數
- concat(c1, c2, …): 字元串拼接
// concat(c1, c2, c3):字元串拼接
String hql = "select concat(name, '_', age) from Student";
List<String> list = session.createQuery(hql, String.class).list();
- substring(c1, begin, len):begin從1開始的
// substring(c1, begin, len):begin從1開始的
String hql = "select substring(name, 2, 2) from Student";
List<String> list = session.createQuery(hql, String.class).list();
- upper(c), lower(c)
- trim(c), length(c)
- abs(c), mod(c), sqrt(c)
- current_date(), current_time()
// 時間相關函數current_date(), current_time(), current_timestamp()
String hql = "select current_date(), current_time(), name from Student";
List<Object[]> list = session.createQuery(hql, Object[].class).list();
- HQL函數
- cast(c as string):類型轉換
- extract(year from c):抽取時間
- year(c), month(c), day(c)
- hour(c), minute(c), second(c)
// year(欄位), month(), day(), hour(), minute(), second()
String hql = "select year(current_date), month(current_date()), name from Student";
List<Object[]> list = session.createQuery(hql, Object[].class).list();
- str(c):轉成字元串
- 集合處理表達式
- size(集合)
// 查找沒有學生的班級
String hql = "from Clazz where size(students)=0";
List<Clazz> list = session.createQuery(hql, Clazz.class).list();
- maxelement(集合), minelement(集合)
- [some|exists|all|any] elements(集合)
-
case表達式
CASE {operand} WHEN {test_value} THEN {match_result} ELSE {miss_result} EN
-
nullif表達式
select nullif(p.nick, p.name) from xxx
子查詢
- where xx in(select xx from tb)
// HQL中,如果條件是對象,實質上指對象的id
String hql = "from Student where clazz in (select c from Clazz c)";
List<Student> list = session.createQuery(hql, Student.class).list();
分組查詢
-
group by 對象 ==> group by 對象.id
-
group by 對象.屬性 having 條件
-
where和having的區別
- where:在分組之前,過濾條件(不可以使用聚合函數)
- having:在分組之後,過濾條件(可以使用聚合函數)
- example1
String hql = "select s.clazz, count(*), min(s.age), max(s.age) from Student s group by s.clazz";
List<Object[]> list = session.createQuery(hql, Object[].class).list();
- example2
String hql = "select s.clazz, count(*), min(s.age), max(s.age) from Student s "
+ "group by s.clazz having max(s.age)>=?1";
List<Object[]> list = session.createQuery(hql, Object[].class).setParameter(1, 40).list();
- example3
// age>=30的學生,再進行分組
String hql = "select s.clazz, count(*), min(s.age), max(s.age) from Student s "
+ "where age>=30 group by s.clazz having max(s.age)>=?1";
List<Object[]> list = session.createQuery(hql, Object[].class).setParameter(1, 40).list();
對象的生命周期
生命周期狀態
- 瞬時狀態(Transient):使用new操作符得到的對象,沒有和資料庫表進行關聯。(資料庫中沒有與之對應的紀錄)
- 持久狀態(Persist):持久對象是任何具有資料庫標識的實例,它由Session統一管理。它們的狀態在事務結束時同資料庫進行同步。(資料庫中有與之對應的紀錄,並受session管理) 對持久狀態對象的修改,會自動同步到資料庫, 同步的同時(set方法)不發sql,在
commit()/flush()
時候統一發sql - 脫管狀態(Detached):Session關閉或調用clear()或evict(),不受Session的管理。(資料庫中有與之對應的紀錄,但不受session管理)
@Test
public void t1() {
Session session = HibernateUtil.openSession();
Transaction tx = session.beginTransaction();
Clazz clazz = new Clazz();
clazz.setName("18-1");
// 此時,clazz對象是瞬時狀態(id是null)
session.save(clazz);
// 此時,clazz對象是持久狀態(id有值)
tx.commit();
HibernateUtil.closeSession();
}
@Test
public void t2() {
Session session = HibernateUtil.openSession();
Transaction tx = session.beginTransaction();
Clazz clazz = session.get(Clazz.class, 2L);
// 此時,clazz對象是持久狀態(id有值)
clazz.setName("abcd");
// 注意:對持久狀態對象的改變,會自動同步到資料庫(可能導致嚴重問題)
// session.saveOrUpdate(clazz); //可選
tx.commit();
HibernateUtil.closeSession();
}
函數
- session.clear():將所有對象從session中逐出
Session session = HibernateUtil.openSession();
Transaction tx = session.beginTransaction();
Clazz clazz = session.get(Clazz.class, 2L);
// 此時,clazz對象是持久狀態(id有值)
// 強制清空session,不進行刷新session(即不與資料庫同步)
session.clear();
// 原session中的所有對象都變成脫管狀態
// 此時,clazz對象是脫管狀態,資料庫中有與之對應的紀錄但不受session管理
clazz.setName("1123");
// 不自動同步到資料庫
// 通過update()重新變成持久狀態
// session.saveOrUpdate(clazz);
tx.commit();
session.close();
- session.evict(obj):只將該對象從session中逐出, 不影響其它對象
Session session = HibernateUtil.openSession();
Transaction tx = session.beginTransaction();
Clazz clazz = session.get(Clazz.class, 2L);
// 此時,clazz對象是持久狀態(id有值)
clazz.setName("aaaaaa");
// saveOrUpdate()此時不會發SQL語句
session.saveOrUpdate(clazz);
// 將clazz對象逐出session
session.evict(clazz);
tx.commit();
session.close();
- session.flush():強制刷新session,與資料庫同步,發SQL語句
Session session = HibernateUtil.openSession();
Transaction tx = session.beginTransaction();
Clazz clazz = session.get(Clazz.class, 2L);
// 此時,clazz對象是持久狀態(id有值)
clazz.setName("aaaaaa");
// 同步session中對象狀態到資料庫
session.flush(); //發SQL語句
// 將clazz對象逐出session
session.evict(clazz);
tx.commit();
session.close();
hibernate什麼時候會SQL語句
save()
需要獲得ID值,當有ID不會發SQL
User u = new User();
u.setName("張三");
u.setAge(20);
session.save(u); //u.id的值由insert語句獲得的,發SQL語句
User4 u = new User4();
u.setName("張三");
u.setAge(20);
session.saveOrUpdate(u); //不發SQL,u.id的值由Hibernate生成
User5 u = new User5();
u.setName("張三");
u.setAge(20);
session.save(u); //u.id的值由資料庫select uuid()生成,發SQL
commit()
前調用flush()
方法,刷新session,與資料庫同步
Clazz clazz = session.get(Clazz.class, 1L);
// 此時,clazz對象是持久狀態(id有值)
clazz.setName("aaaaaa"); // 不發sql
// 同步session中對象狀態到資料庫
session.flush(); //發SQL語句
clazz.setName("bbbb");
tx.commit();//發SQL
commit()
提交事務時(之前沒有flush()
,或者flush()
後沒有改變對象)
delete()
,update()
,save()有ID時
不會發SQL,而是等到commit()
統一發
Clazz clazz = session.get(Clazz.class, 1L);
// 此時,clazz對象是持久狀態(id有值)
clazz.setName("aaaaaa"); // 不發sql
// 同步session中對象狀態到資料庫
session.flush(); //發SQL語句
//clazz.setName("bbbb");
tx.commit();//不發SQL
get()
方法查詢資料庫
其他註解
@Where(只映射滿足條件的紀錄)
用於類上,用於集合屬性上
@Entity
@Table(name = "tb_clazz")
@Where(clause = "delFlag is null")
public class Clazz {
@OrderBy
用於集合屬性上,按資料庫表欄位進行排序(簡單排序)
@OrderBy(“所標註屬性的欄位 desc, 所標註屬性的欄位 asc”)
@OrderBy("age desc, name asc")
private Set<Student> students = new HashSet<>();
@SortComparator
用於集合屬性上,按業務邏輯進行Java排序(複雜排序)
@SortComparator(StudentComparator.class)
private Set<Student> students = new HashSet<>();
Hibernate快取
懶載入與迫切載入
get() load()
- get()
-
get():立即發SQL,返回Clazz類型對象
-
如果資料庫沒有該記錄,則返回null,用到該對象時拋NullPointerException
// get():立即發SQL,返回Clazz類型對象
// 如果資料庫沒有該記錄,則返回null,用到該對象時拋NullPointerException
Clazz clazz = session.get(Clazz.class, 1L);
- load()
-
load():不立即發SQL(延遲載入),返回一個代理對象
-
在必要的時候,才發SQL查詢(查詢到的結果放在對象的target屬性上)如DB沒有該記錄,仍然會返回代理對象,用到該對象時拋ObjectNotFoundException
// load():不立即發SQL(延遲載入),返回一個代理對象
// 在必要的時候,才發SQL查詢(查詢到的結果放在對象的target屬性上)
// 如果資料庫沒有該記錄,仍然會返回代理對象,用到該對象時拋ObjectNotFoundException
Clazz clazz = session.load(Clazz.class, 1L);
懶載入(延遲載入)
-
一對多:默認使用延遲載入(懶載入), 在必要的時候,會發SQL載入
-
代表函數:
load()
-
註解:
@OneToMany(fetch=FetchType.LAZY)
不要改變”一對多”中默認的懶載入, 如果確實需要立即載入,使用HQL中的join fetch
@OneToMany(mappedBy = "clazz", fetch=FetchType.LAZY)// 默認LAZY
private Set<Student> students = new HashSet<>();
// 不要改變"1對多"中默認的懶載入, 如果確實需要立即載入,使用HQL中的join fetch
// join fetch:迫切連接
String hql = "select distinct c from Clazz c left join fetch c.students";
List<Clazz> list = session.createQuery(hql, Clazz.class).list();
迫切載入
-
多對一: 默認使用迫切載入
-
代表函數:
get()
-
註解:
@ManyToOne(fetch = FetchType.EAGER)
Student student = session.get(Student.class, 1L);//立即載入關聯對象
一級快取(session級快取)
-
持久化對象,受session管理(持久化對象是放在session快取中),所有查詢出來的實體對象,一個一個地都放在session快取中(不是集合對象),快取只在同一個sessoin中有效
-
get()和load()會先優先從一級快取中取值
-
session.close(), session.clear(), session.evict(obj)會影響session快取
- example1: load()使用快取
Session session = HibernateUtil.openSession();
// 發SQL
Clazz clazz = session.get(Clazz.class, 1L);
System.out.println(clazz.getName());
// 獲得session快取中的數據
Set keys = session.getStatistics().getEntityKeys();
System.out.println(keys);
// 不發SQL:使用快取
Clazz clazz2 = session.load(Clazz.class, 1L);
System.out.println(clazz2.getName());
HibernateUtil.closeSession();
- example2: clear()影響快取
// 清空session快取
session.clear();
// 發SQL
Clazz clazz2 = session.load(Clazz.class, 1L);
System.out.println(clazz2.getName());
- example3: 個題對象形式存在
// 一個一個地都放在session快取中(不是集合對象)
//查詢到的結果集list中的每個實體對象,都會放在session快取
String hql = "from Clazz";
List<Clazz> list = session.createQuery(hql, Clazz.class).list();
System.out.println(list.size());
// 查詢到的結果集list中的每個實體對象,都會放在session快取
// 獲得session快取中的數據
Set keys = session.getStatistics().getEntityKeys();
System.out.println(keys);
// 不發SQL:使用快取
Clazz clazz2 = session.load(Clazz.class, 1L);
System.out.println(clazz2.getName());
- example4: 迫切載入的對象也會被快取
// 載入學生,會立即載入班級
Student student = session.get(Student.class, 1L);
System.out.println(student.getName());
// 獲得session快取中的數據
Set keys = session.getStatistics().getEntityKeys();
System.out.println(keys);
// 不發SQL:使用快取
Clazz clazz2 = session.load(Clazz.class, 2L);
System.out.println(clazz2.getName());
二級快取(sessionFactory級快取)
ehcache基本知識
-
二級快取常用ehcache快取,是一個執行緒級快取,即運行在JVM中
-
持久化狀態對象,會放入二級快取
-
get()和load()會先使用session快取,再使用二級快取,如果快取沒有則查詢資料庫
ehcache開啟步驟
-
放入ehcache的jar包
-
編寫ehcache.xml配置文件
-
在hibernate.cfg.xml中開啟二級快取
-
在實體類上或實體集合屬性上使用@Cache註解
ehcache.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="//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中註冊開啟
- 實體類
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Clazz {
- 屬性集合
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<Student> students = new HashSet<>();
查詢快取(屬於二級快取)
介紹
- 快取查詢語句和對應的結果集
- key: 查詢語句(條件參數不同,是不同的key)
- value:查詢到的結果集(一般是集合)
- 開啟查詢快取:
setCacheable(true)
- 先使用查詢快取(屬於二級),如果沒有則查詢資料庫,並將查詢結果放入查詢快取
example
String hql = "from Clazz";
List<Clazz> list = session.createQuery(hql, Clazz.class).setCacheable(true).list();
System.out.println(list);
String hql2 = "select c from Clazz c";
List<Clazz> list2 = session.createQuery(hql2, Clazz.class).setCacheable(true).list();
System.out.println(list2);
快取機制的其他問題
- 快取的目的,查詢時不用去資料庫查詢而直接從記憶體讀入
- 快取的數據和資料庫的紀錄必須保持一致,或者快取不可用
一級快取溢出
如果循環插入的數據量比較多時,需要及時清空session快取
save(s)之後,s對象是持久狀態的,它存儲在session快取中,session快取的記憶體,可能記憶體溢出
for(int i=1; i<=1000000; i++) {
Student s = new Student();
s.setName("test_" + i);
session.saveOrUpdate(s);
// 如果循環插入的數據量比較多時,需要及時清空session快取
// save(s)之後,s對象是持久狀態的,它存儲在session快取中
// session快取的記憶體,可能記憶體溢出
if(i%1000 == 0) {
session.flush(); //強制同步資料庫
session.clear(); //清空session快取,避免記憶體溢出
}
}
save()/update()/delete()隻影響快取中單個實體
- 一個session中,get()同一個ID時,不會去二級快取查找
Clazz clazz = session.get(Clazz.class, 1L);
session.clear(); //清空session快取,但不影響二級快取
// 插入一條新記錄
Clazz c = new Clazz();
c.setName("17-11");
session.saveOrUpdate(c); //此時,會發SQL(id是資料庫自增)
session.clear();
// 一個session中,get()同一個ID時,不會去二級快取查找
session = HibernateUtil.openSession();
clazz = session.get(Clazz.class, 1L);
- 驗證
Session session = HibernateUtil.openSession();
Clazz clazz = session.get(Clazz.class, 1L);
System.out.println("=========================: " + clazz.getId());
HibernateUtil.closeSession();
// 更新一條記錄
session = HibernateUtil.openSession();
Transaction tx = session.beginTransaction();
Clazz c2 = session.get(Clazz.class, 2L);
c2.setName("aaa");
session.saveOrUpdate(c2); //非必要
tx.commit(); //如果session中對象和資料庫中對象不一致,則發SQL
HibernateUtil.closeSession();
// 二級快取中數據有效:不發SQL
session = HibernateUtil.openSession();
clazz = session.get(Clazz.class, 1L); //二級快取(Clazz#1有效)
System.out.println("=========================: " + clazz.getId());
HibernateUtil.closeSession();
update,delete語句進行批量操作會影響整個實體快取
update, delete語句進行批量操作時,會快取整個實體快取(全部不可用)
Session session = HibernateUtil.openSession();
Clazz clazz = session.get(Clazz.class, 1L);
System.out.println("=========================: " + clazz.getId());
HibernateUtil.closeSession();
// 更新一條記錄
session = HibernateUtil.openSession();
Transaction tx = session.beginTransaction();
String hql = "update Clazz set name=?1 where id=2"; //將Clazz相關快取對象全部標識不可用
session.createQuery(hql).setParameter(1, "bbbb").executeUpdate();
tx.commit();
HibernateUtil.closeSession();
// 二級快取中數據無效,會發SQL
session = HibernateUtil.openSession();
clazz = session.get(Clazz.class, 1L); //必須去資料庫查詢
System.out.println("=========================: " + clazz.getId());
HibernateUtil.closeSession();
Spring XML
面向對象設計原則
-
開閉原則(The Open-Closed Principle ,OCP)
- 對擴展開放
- 對修改關閉
-
里氏替換原則(Liskov Substitution Principle ,LSP)
- 子類應當可以替換基類並出現在基類能夠出現的任何地方
- 子類可以擴展父類的功能,但不能改變父類原有的功能
-
迪米特原則(最少知道原則)(Law of Demeter ,LoD)
- 降低類之間的耦合,盡量減少對其他類的依賴
- 是否可以減少public方法和屬性,是否可以修改為private等
-
單一職責原則
- 只能讓一個類/介面/方法有且僅有一個職責
- 所謂一個類的一個職責是指引起該類變化的一個原因
-
介面分隔原則(Interface Segregation Principle ,ISP)
- 一個類對一個類的依賴應該建立在最小的介面上
- 建立單一介面,不要建立龐大臃腫的介面
- 盡量細化介面,介面中的方法盡量少
單一職責強調的是介面、類、方法的職責是單一的,強調職責
介面分隔原則主要是約束介面,針對抽象、整體框架 -
依賴倒置原則(Dependency Inversion Principle ,DIP)
- 高層模組不應該依賴於低層模組,二者都應該依賴於抽象
- 抽象不應該依賴於細節,細節應該依賴於抽象
- 針對介面編程,不要針對實現編程
-
組合/聚合復用原則(Composite/Aggregate Reuse Principle ,CARP)
- 盡量使用組合/聚合,不要使用類繼承。
-
關聯關係與依賴關係區分
- 關聯關係:一般關聯、組合、聚合, has a
class A {
private B b; //關聯
}
- 依賴關係
class A {
public void test(B b) {}
}
- 繼承關係 is a
控制反轉IoC和依賴注入DI
-
IoC和DI是在不同的角度講同一件事件,一般使用IoC
-
IoC,對於spring框架來說,就是由spring來負責控制對象的生命周期和對象間的關係。
-
依賴注入的三種方式:
- 構造器注入
- setter方法注入
- 註解注入
- 依據現有bean注入
- 只有spring容器創建的, 才會依賴注入, new的不會注入
UserService userService = new UserService();
// userService對象不是spring容器創建的, 那麼無法依賴注入
System.out.println(userService.getUserDao());
ApplicationContext atx = new ClassPathXmlApplicationContext("beans2.xml");
UserService userService2 = atx.getBean(UserService.class);
// userService2對象是spring容器創建的, 會依賴注入
System.out.println(userService2.getUserDao());
Spring環境搭建
-
加入Spring框架相關的jar包
-
編寫Spring的配置文件
- xml方式
- 註解方式
-
實例化ApplicationContext,從spring容器中獲得對象
-
Bean的配置
Spring核心技術
- IoC:控制反轉
- 依賴注入(DI):注入依賴對象(屬性)
- 控制反轉和依賴注入是在不同的角度講同一件事件,一般使用控制反轉
- AOP:面向切面編程
動態代理
Spring歷史
spring 1.2
spring 2.0:xml配置
spring 2.5:引入註解配置
spring 3.x:mvc的改進
spring 5.x:新特性
Spring XMl配置文件
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="//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
<property name="xx" value="字元串值"/>
<property name="xx" ref="引用bean的id"/>
或者:
<property name="xx">
<ref bean="引用bean的id"/>
</property>
- 使用內嵌bean:
<property name="xx">
<bean class="類型"/>
</property>
依賴對象的注入
<bean id="userService" class="com.kzw.service.UserService">
<!-- 對類的屬性注入依賴對象 -->
<property name="dao" ref="userDao"/>
</bean>
-
相當於
setXx()
-
注入方式
- setter注入,對於屬性
- 構造器注入,對於類
-
依據現有bean注入,對於類
- 註解
-
注入形式
- ref: 通過ID引用另一個bean
- value: 設置一個值(字元串或數值等)
帶參構造器注入
<!--1-->
<!--
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
String str = df.format(date);
-->
<!-- 帶參構造器注入 -->
<bean id="df" class="java.text.SimpleDateFormat">
<constructor-arg value="yyyy-MM-dd HH:mm:ss"/>
</bean>
<!-- 無參構造器注入 -->
<bean id="date" class="java.util.Date" scope="prototype"/>
<!--2-->
<!-- 通過構造器方式注入:調用某帶參的構造方法 -->
<bean id="user2" class="com.kzw.bean.User" primary="true">
<constructor-arg value="100"/>
<constructor-arg value="李四"/>
</bean>
依據現有bean普通方法注入
- xml1
<!-- String str = df.format(date); -->
<bean id="df" class="java.text.SimpleDateFormat">
<constructor-arg value="yyyy-MM-dd HH:mm:ss"/>
</bean>
<bean id="date" class="java.util.Date" scope="prototype"/>
<bean id="str" factory-bean="df" factory-method="format">
<constructor-arg ref="date"/>
</bean>
- xml2
<!-- 使用普通方法返回對象(不帶參數) -->
<bean id="factory" class="com.kzw.factory.Factory2"/>
<bean id="user5" factory-bean="factory"
factory-method="getInstanse"/>
<!-- 使用普通方法返回對象(帶參數) -->
<bean id="user6" factory-bean="factory"
factory-method="getInstanse">
<constructor-arg value="200"/>
<constructor-arg value="李四22"/>
</bean>
- java
public class Factory2 {
public User getInstanse() {
return new User(2L, "李四");
}
public User getInstanse(Long id, String name) {
return new User(id, name);
}
}
使用靜態方法注入對象
- 在無現有bean情況下調動方法注入,注意要為static方法
- xml
<!-- 使用靜態方法返回對象(不帶參數) -->
<bean id="user3" class="com.kzw.factory.Factory1"
factory-method="getInstanse"/>
<!-- 使用靜態方法返回對象(帶參數) -->
<bean id="user4" class="com.kzw.factory.Factory1"
factory-method="getInstanse">
<constructor-arg value="100"/>
<constructor-arg value="張三11"/>
</bean>
- java
public class Factory1 {
public static User getInstanse() {
return new User(1L, "張三");
}
public static User getInstanse(Long id, String name) {
return new User(id, name);
}
}
內嵌bean注入
<bean id="d" class="java.util.Date" scope="prototype"/>
<bean id="df" class="java.text.SimpleDateFormat">
<constructor-arg value="yyyy-MM-dd HH:mm:ss"/>
</bean>
<bean id="str" factory-bean="df" factory-method="format" scope="prototype">
<constructor-arg ref="d"/>
</bean>
<!-- 內嵌bean -->
<bean id="str2" factory-bean="df" factory-method="format" scope="prototype">
<constructor-arg>
<!-- 定義局部bean,不需要配置id -->
<bean class="java.util.Date"/>
</constructor-arg>
</bean>
集合屬性注入
- xml
<!-- 集合屬性的注入 -->
<bean id="mybean" class="com.kzw.bean.MyBean">
<property name="array">
<array>
<value>111</value>
<value>111</value>
<value>111</value>
</array>
</property>
<property name="list">
<list>
<value>aaa</value>
<value>aaa</value>
<value>aaa</value>
</list>
</property>
<property name="set">
<list>
<value>aaa</value>
<value>aaa</value>
<value>aaa</value>
</list>
</property>
<property name="map">
<map>
<entry key="id" value="100"/>
<entry key="name" value="張三"/>
<entry key="birthday" value-ref="date"/>
</map>
</property>
<property name="props">
<props>
<prop key="id">200</prop>
<prop key="name">李四</prop>
<prop key="age">30</prop>
</props>
</property>
<property name="props2">
<value>
id=300
name=\u738b\u4e94
age=30
</value>
</property>
<property name="cls" value="com.kzw.bean.User"/>
</bean>
- java
public class MyBean {
private String[] array;
private List<String> list;
private Set<String> set;
private Map<String, Object> map;
private Properties props;
private Properties props2;
private Class<?> cls;
bean的生命周期
-
init-method=”方法名”:在bean實例化並依賴注入完成之後,調用該方法
-
destroy-method=”方法名”:在spring容器關閉之前,調用該方法
-
使用註解
- xml文件
<!-- 通過setter方式注入: 要求有無參構造方法 -->
<bean id="user1" class="com.kzw.bean.User" init-method="init" destroy-method="close" lazy-init="true">
<property name="id" value="1" />
<property name="name" value="張三" />
</bean>
- java文件
private Long id;
private String name;
private Date ctime;
public void init() {
System.out.println("初始化:" + id + ", " + name);
}
public void close() {
System.out.println("銷毀:" + id);
}
屬性編輯器
作用
將字元串類型轉換成屬性需要的類型
<!-- 識別value -->
<property name="age" value="20"/>
<property name="date" value="2020-04-06 10:20"/>
<!-- value="1,張三,20" ==> User(id=1, name="張三", age=30) -->
<property name="card" value="200,1122,北京"/>
步驟
- 編寫屬性編輯器
- 註冊屬性編輯器
屬性編輯器方式1–PropertyEditorSupport
- XML文件
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<!-- 對註冊器對象的屬性進行map類型注入 -->
<property name="customEditors">
<map>
<entry key="java.util.Date"
value="com.kzw.editor.MyDateEditor" />
<entry key="com.kzw.bean.Card"
value="com.kzw.editor.MyCardEditor"/>
</map>
</property>
</bean>
- MyDateEditor文件
public class MyDateEditor extends PropertyEditorSupport {
/**
* 處理字元串,並包裝成需要的某類型對象
*/
@Override
public void setAsText(String text) throws
IllegalArgumentException {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm");
try {
Date date = df.parse(text);
// 包裝成對象,並進行賦值, 然後spring將此對象注入
this.setValue(date);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
- MyCardEditor文件
public class MyCardEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws
IllegalArgumentException {
String[] arr = text.split(",");
Card c = new Card();
c.setId(Long.parseLong(arr[0]));
c.setSno(arr[1]);
c.setAddr(arr[2]);
setValue(c);
}
}
屬性編輯器方式2–PropertyEditorRegistrar
- XML文件
<!-- 註冊屬性編輯器 -->
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<array>
<bean class="com.kzw.editor.MyEditorRegistrar">
<!-- 對註冊器對象的屬性進行注入 -->
<property name="format"
value="yyyy-MM-dd HH:mm"/>
</bean>
</array>
</property>
</bean>
- MyEditorRegistrar文件
public class MyEditorRegistrar implements PropertyEditorRegistrar {
private String format = "yyyy-MM-dd HH:mm:ss";
@Override
public void registerCustomEditors(
PropertyEditorRegistry registry) {
// 註冊Date類型的屬性編輯器
DateFormat df = new SimpleDateFormat(format);
registry.registerCustomEditor(
Date.class, new CustomDateEditor(df, true));
// 註冊Card類型的屬性編輯器
registry.registerCustomEditor(
Card.class, new MyCardEditor());
}
public void setFormat(String format) {
this.format = format;
}
}
獲取bean
- 初始化Spring容器
ApplicationContext atx = new ClassPathXmlApplicationContext("xx.xml");
- 獲得bean
atx.getBean(UserService.class);
atx.getBean("userService", UserService.class);
// 初始化Spring容器
ApplicationContext atx = new ClassPathXmlApplicationContext("beans.xml");
// 根據類型獲得bean
UserService service = atx.getBean(UserService.class);
service.save();// 調用方法
// 根據ID獲得bean
UserService service2 = atx.getBean("userService", UserService.class);
service2.save();// 調用方法
使用ApplicationContext.getBean() 獲取bean:
- 根據類型獲得bean:
- 比較方便
- 如果同一個類型有多個bean定義時,會報錯(可以指定primary=”true”)
2、根據id獲得bean:
- id是唯一,所以能返回一個對象
- 如果聲明轉換類型不一致,會報錯
Spring 註解
依賴注入註解
注意:需要開啟依賴注入註解功能
<context:annotation-config/>
- @Value:注入字元串,使用屬性編輯器進行類型轉換
- @Autowired:spring提供,只根據類型進行注入(類型兼容,介面,繼承等等)
- @Resource:jdk提供,先根據屬性名在spring容器中根據id查找bean,如果沒有則根據類型查找
– @Resource可能需要查2次- 根據屬性名查找到的bean,如果bean的類型和屬性的類型不一樣,會報錯
Bean定義註解
注意:需要開啟組件自動掃描
<context:component-scan base-package="com.bfs" />
掃描指定路徑下的所有類中的 @Component, @Repository, @Service, @Controller多個路徑以逗號隔開,被掃描到的成為Spring中的Bean,如果裡面有依賴注入註解,便會進行依賴注入
- @Repository:通常用在DAO類上
- @Service:通常用於Service類上
- @Controller:通常用於Action類上
- @Component:用於其它情況
這四個註解,功能(定義Bean)是一樣的。在spring mvc中,@Controller有特殊用途
- xml
<!-- 開啟依賴注入的註解 -->
<context:annotation-config/>
<!-- 開啟bean定義的註解:組件自動掃描-->
<context:component-scan base-package="com.kzw2" />
<!-- 註冊屬性編輯器 -->
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<array>
<bean class="com.kzw.editor.MyEditorRegistrar">
<property name="format" value="yyyy-MM-dd HH:mm"/>
</bean>
</array>
</property>
</bean>
- java
@Component
public class User {
@Value("1")
private Long id;
@Value("張三")
private String name;
@Value("2020-04-06 10:20")
private Date ctime;
@Autowired
private Card card;
Spring配置註解(完全使用註解)
Spring配置相關註解
- @Configuration:表示這是一個spring配置文件
- @ComponentScan:開啟組件自動掃描(掃描4個註解),默認basePackages為當前類所在目錄(即./*)
- @Bean:定義一個bean,bean的id為方法名稱
注意:@Bean定義的方法,在Spring容器初始化調用的 - @Scope:bean默認是單例模式,指定原型模式 @Scope(“prototype”)
- @Qualifier:微調器,根據id指定一個bean
- @DependsOn:該bean定義要依賴於另一個bean的定義
- @PostConstruct:該對象創建完成之後調用,相當於 init-method
- @PreDestroy:該對象銷毀時調用,相當於 destroy-method.
- @Primary: 根據類型查找時,優先順序最高
- @PropertySource(“classpath:/jdbc.properties”) :載入屬性文件
依賴注入方式
方法的參數,會自動依賴注入
注意: 根據參數注入,若是根據類型尋找的,當有多個的時候,如果沒有@Primary會報錯
- 根據類型
@Bean
@Scope("prototype")
public Date ctime(){
return new Date();
}
@Bean
@Scope("prototype")
@Primary
public Date ctime2(){
return new Date();
}
@Bean
public User user2(Date date){
- 根據id
// 根據id注入bean
@Bean
public String bean2(@Qualifier("user2") User user) {
System.out.println("bean2: " + user);
return "bean2";
}
調用另一個標註了@Bean的方法
@Bean // bean的id=user1
public User user1() {xxx;}
User user1 = user1(); // 依賴注入
註解形式的配置文件
基礎配置和依賴注入
/**
* Spring的配置文件(註解方式)
*/
@Configuration
@ComponentScan("com.kzw")
public class AppConfig {
@Autowired
private UserService userService;
@Bean // bean的id=user1
@Primary // 根據類型查找時,優先順序最高
public User user1() {
System.out.println("初始化:user1");
User u = new User(2L, "李四");
u.setCtime(new Date());
userService.save(u);
return u;// 注意要返回
}
@Bean
@Scope("prototype")
public Date ctime() {
System.out.println("初始化:ctime");
return new Date();
}
// 依賴注入方式1:方法的參數,會自動依賴注入
@Bean
public User user2(Date date) {
System.out.println("初始化:user2");
User u = new User(3L, "王五");
u.setCtime(date);
return u;
}
// 依賴注入方式2:調用另一個標註了@Bean的方法
@Bean
public User user3() {
Date date1 = ctime(); // 依賴注入
Date date2 = ctime();
System.out.println(date1 == date2); // false,原型模式
User user1 = user1(); // 依賴注入
User user2 = user1();
System.out.println(user1 == user2); // true,單例模式
User u = new User(4L, "小劉");
u.setCtime(date1);
return u;
}
/**
* 使用參數方式注入一個UserService類型的bean
* 調用方法方式注入一個User類型對象
*/
@Bean
@DependsOn("bean2")
public String bean1(UserService userService) {
// 此外並不是調用方法,而是獲得一個id=user3的bean
User user = user3();
System.out.println("bean1: " + user);
userService.save(user);
return "bean1";
}
// 根據id注入bean
@Bean
public String bean2(@Qualifier("user2") User user) {
System.out.println("bean2: " + user);
return "bean2";
}
}
其他註解
@Configuration
@PropertySource("classpath:/jdbc.properties") // 載入屬性文件
public class AppConfig2 {
// 方式1:注入Environment對象
@Autowired
private Environment env;
@Bean
public String test1() {
String username = env.getProperty("jdbc.username");
System.out.println("username: " + username);
return "test1";
}
// 為了可以使用@Value("${xxx}")
@Bean
public static PropertySourcesPlaceholderConfigurer
placeholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
// 方式2:使用@Value("${xxx}"),但是必須加上的配置
@Value("${jdbc.password}")
private String password;
@Bean
public String test2() {
System.out.println("test2, password: " + password);
return "test2";
}
@Bean // 屬性註解
public String test3(@Value("${jdbc.username}") String username)
{
System.out.println("test3, username: " + username);
return "test2";
}
}
註解配置的屬性編輯器
FormattingConversionService+自帶(默認)屬性編輯器
- 此處bean的id=conversionService,不能隨便寫
- 該bean在spring內部通過bean的id查找
- 有多個屬性編輯器,
conversionService.addFormatter
繼續添加
/**
* 屬性編輯器: 方式1
* 此處bean的id=conversionService,不能隨便寫
* 原因:在spring內部通過bean的id查找
*/
@Bean
public FormattingConversionService conversionService() {
FormattingConversionService conversionService =
new DefaultFormattingConversionService(false);
conversionService.addFormatter(
new DateFormatter("yyyy-MM-dd HH:mm"));
return conversionService;
}
CustomEditorConfigurer+自定義屬性編輯器
- 此處bean的id可以隨便寫,但是方法必須為static
- 該bean在spring內部通過bean的類型查找
- 有多個屬性編輯器,
map.put(目標類型.class,自定義屬性編輯器.class);
繼續添加
// 屬性編輯器: 方式2
// 必須為static方法
@Bean
// 必須static
public static CustomEditorConfigurer editorConfigurer() {
Map<Class<?>, Class<? extends PropertyEditor>> map =
new HashMap<>();
//map.put(目標類型.class,自定義屬性編輯器.class);
map.put(Date.class, MyDateEditor.class);
CustomEditorConfigurer cfg = new CustomEditorConfigurer();
cfg.setCustomEditors(map);
return cfg;
}
Spring aop
代理
靜態代理:代理設計模式
代理類是自己定義的
動態代理:代理類在JVM中動態創建的
-
JDK動態代理:使用JDK中的Proxy類,動態創建代理對象(前提:目標對象類必須實現了業務介面), 優先使用JDK的動態代理
- 原理:根據目標類對象實現的介面,動態創建一個類也實現這些介面(目標對象類和代理對象類是兄弟關係,都實現相同介面)
-
CGLIB動態代理:第三方提供,動態創建代理對象。(目標對象類可以不實現任何介面)
- 原理:動態創建目標對象類的子類(目標對象類是代理對象類的父類)