SpringData 完全入門指南
SpringData 筆記
1. 配置專案
1.pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.lwen</groupId> <artifactId>SpringData</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!--MySQL Driver--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> </dependency> <!--spring--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.3.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.5.RELEASE</version> </dependency> <!--spring data jpa--> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-jpa</artifactId> <version>1.8.0.RELEASE</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>4.3.6.Final</version> </dependency> </dependencies> </project>
2.bean.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!--1 配置資料來源--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="username" value="root"/> <property name="password" value=""/> <property name="url" value="jdbc:mysql://localhost:3306/spring_data"/> </bean> <!--2 配置EntityManagerFactory--> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/> </property> <property name="packagesToScan" value="com.lwen"/> <property name="jpaProperties"> <props> <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop> <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.format_sql">true</prop> <prop key="hibernate.hbm2ddl.auto">update</prop> </props> </property> </bean> <!--3 配置事務管理器--> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean> <!--4 配置支援註解的事務--> <tx:annotation-driven/> <!--5 配置spring data--> <jpa:repositories base-package="com.lwen" entity-manager-factory-ref="entityManagerFactory"/> <context:component-scan base-package="com.lwen"/> </beans>
3.實體類
package com.lwen.entry; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; /** * Student實體類 */ @Entity public class Student { @[email protected] private int id; private String name; private int age; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } }
這裡注意的一點就是我們在使用註解的時候一定要注意匯入的包,我們都是匯入的javax中的類。
2.Repository
1. 使用
這個東西是SpringData的核心,但是我們實際去看的時候會發現他是一個空介面,也就是這個一個標記介面。我們自己的介面必須繼承這個接口才會具備查詢的功能,所以說我們的自定的查詢器必須要繼承這個介面。這個介面是泛型介面也就是我們需要輸入兩個引數,第一個就是我們查詢器的型別,針對於那個表進行查詢,另外一個就是表的Id的型別,這個型別必須是序列化介面的子型別,所以說不能使用基本型別,我們只能使用包裝型別。
public interface EmployeeRepository extends Repository<Employee,Integer> {
Employee findByName(String name);
}
但是我們還有另外一種方式,就是使用註解的方式不用繼承這個介面。
@RepositoryDefinition(domainClass = Student.class,idClass = Integer.class)
public interface StudentRepository {
}
2. 常用的子介面
- CrudRepository:繼承Repository,實現了CRUD相關的方法
- PagingAndSortingRepository:繼承CrudRepository,實現了分頁排序相關的方法
- JpaRepository:繼承PagingAndSortingRepository,實現JPA規範相關的方法
這些介面的功能都是非常強大並且實用的我們在使用的時候就可以直接繼承這些介面。
3.查詢規則
1.約定方法簽名查詢
2.手動查詢 @Query
1.複雜查詢
@Query("select o from Employee o where id = (select max(id) from Employee)")
Employee getMaxIdEmployee();
2.佔位符查詢
@Query("select o from Employee o where name=?1 and age=?2")
Employee getByNameAndAge(String name,Integer age);
@Query("select o from Employee o where name like %?1%")
Employee getByLikeName(String name);
3.命名引數
@Query("select o from Employee o where name=:name and age=:age")
Employee getByNameAndAge1(@Param("name") String name,@Param("age") Integer age);
4.原生態查詢
@Query(nativeQuery = true,value = "select * from spring_data.employee where name = ?1")
void getNative(String name);
3. 更新刪除操作
在SpringData中使用插刪改操作的時候我們必須定義一個Service層,然後我們在Service層呼叫Dao的Repository來更新資料庫,接著我們需要將那個Repository的方法設定為 @Modifying ,最後最重要的就是我們在Service層的那個方法中寫 @Transactional 註解。才能更新成功,所以所有的事務只能出現在 Service 層。但是注意因為我們的Service沒有繼承任何的Spring相關的東西我們要把它放到容器的時候需要使用@Service註解,否則是不行的。
@Modifying
@Query("update Employee o set o.name=:name where o.id=:id")
void update(@Param("id") Integer id,@Param("name") String name);
@Service
public class EmployeeService {
@Autowired
EmployeeRepository repository;
@Transactional //這個是javax裡面的
public void update(){
repository.update(1, "lwenxu");
}
}
小提示,很多時候我們發現有些東西在Spring中有在javax中也有,我們優先匯入Javax中的,如果出現了什麼方法無法呼叫估計就是包導錯了。
3. 常用的Repository
這三個高階的Repository實際上是從上到下依次繼承的。
1. CrudRepository
我們的Repository必須要繼承這個介面,然後我們就有crud的一些操作了。接著我們需要建立service層,然後再service中使用事務,並且注入我們的Repository,這裡我們用了一張新的表我們在bean上指定我們的表名就是使用@Table(name = "employee_test") 註解。最後進行save操作。
@Transactional
public void saveAll(List<Employee> employees){
employeeCrudRepository.save(employees);
}
EmployeeCrudRepository employeeCrudRepository = ctx.getBean(EmployeeCrudRepository.class);
ArrayList<Employee> employees = new ArrayList<Employee>();
for (int i = 0; i < 100; i++) {
employees.add(new Employee(i, "lwen" + i, i));
}
employeeCrudRepository.save(employees);
2.PagingAndSortingRespository
他是分頁和排序功能。
EmployeePageAndSortRepository pageAndSort = ctx.getBean(EmployeePageAndSortRepository.class);
//建立一個排序器,是按照id的降續排列的
Sort sort = new Sort(new Sort.Order(Sort.Direction.DESC, "id"));
//第一個引數是當前的頁碼他是從0開始的
//第二個引數就是每一頁的大小
//第三個引數是可選引數,傳入一個sort,就是按照哪種方式分頁 由於這裡用的是降續所以出來的結果應該是 0 在最後一頁 99在第一頁
Pageable pageable = new PageRequest(0, 9, sort);
Page<Employee> page = pageAndSort.findAll(pageable);
//獲取當前頁的內容
System.out.println(page.getContent());
//獲取所有的頁數
System.out.println(page.getTotalPages());
3.JpaRepository
- findAll
- save(entries)
- deleteInBatch
- findAll(sort)
- flush
4. JpaSpecificationExecutor
這裡吧這個介面單獨拿出來說主要是因為這個介面實際上不是繼承自 Repository 這個介面,他的作用從表面上貌似也是看不出來,實際上我們前面看到我們可以進行簡單的分頁,但是那些分頁並沒有一個讓我們傳入查詢條件的地方,我們這個介面就是實現了這個功能也就是有條件的分頁查詢。
jpa介面需要繼承 JpaSpecificationExecutor 然後這裡就包含了五個方法,分別是 findAll(三個過載) 和 findOne 以及 count 。
然後在使用的時候只調用對應的方法即可,Page
new Specification<User>() {
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
return criteriaBuilder.gt(root.<Number>get("id"),4);
}
}
可以看到這個函式式接口裡面的第一個引數是 root 這個相當於一個導航器,也就是用它可以獲取到我們實體類中的屬性,也就是我們獲取到表的欄位,CriteriaQuery 則是可以進行語句的拼裝,裡面有 where ,groupby 以及having 等方法,進行sql組合的。二最後一個引數就是 CriteriaBuilder 用來建立 Predicate 物件的,也就是生成查詢條件物件。
上面的程式生成的最後的條件就是獲取id大於4 的所有資訊,然後分頁展示。
5. 表的對映關係
1.一對一
一對一關係這裡定義了一個Person物件以及一個IDCard物件
Person類:
@Entity
@Table(name="t_person")
public class Person
{
private int id;
private String name;
private IDCard card;
@OneToOne(mappedBy="person") ---> 指定了OneToOne的關聯關係,mappedBy同樣指定由對方來進行維護關聯關係
public IDCard getCard()
{
return card;
}
public void setCard(IDCard card)
{
this.card = card;
}
@Id
@GeneratedValue
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
IDCard類:
@Entity
@Table(name="t_id_card")
public class IDCard
{
private int id;
private String no;
private Person person;
@Id
@GeneratedValue
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getNo()
{
return no;
}
public void setNo(String no)
{
this.no = no;
}
@OneToOne ---> OnetoOne指定了一對一的關聯關係,一對一中隨便指定一方來維護對映關係,這裡選擇IDCard來進行維護
@JoinColumn(name="pid") ---> 指定外來鍵的名字 pid
public Person getPerson()
{
return person;
}
public void setPerson(Person person)
{
this.person = person;
}
}
注意:在判斷到底是誰維護關聯關係時,可以通過檢視外來鍵,哪個實體類定義了外來鍵,哪個類就負責維護關聯關係。
2.多對一
這裡我們定義了兩個實體類,一個是ClassRoom,一個是Student,這兩者是一對多的關聯關係。
ClassRoom類:
@Entity
@Table(name="t_classroom")
public class ClassRoom
{
private Set<Student> students;
public ClassRoom()
{
students = new HashSet<Student>();
}
public void addStudent(Student student)
{
students.add(student);
}
@OneToMany(mappedBy="room") ---> OneToMany指定了一對多的關係,mappedBy="room"指定了由多的那一方來維護關聯關係,mappedBy指的是多的一方對1的這一方的依賴的屬性,(注意:如果沒有指定由誰來維護關聯關係,則系統會給我們建立一張中間表)
@LazyCollection(LazyCollectionOption.EXTRA) ---> LazyCollection屬性設定成EXTRA指定了當如果查詢資料的個數時候,只會發出一條 count(*)的語句,提高效能
public Set<Student> getStudents()
{
return students;
}
public void setStudents(Set<Student> students)
{
this.students = students;
}
}
Student類:
@Entity
@Table(name="t_student")
public class Student
{
private ClassRoom room;
@ManyToOne(fetch=FetchType.LAZY) ---> ManyToOne指定了多對一的關係,fetch=FetchType.LAZY屬性表示在多的那一方通過延遲載入的方式載入物件(預設不是延遲載入)
@JoinColumn(name="rid") ---> 通過 JoinColumn 的name屬性指定了外來鍵的名稱 rid (注意:如果我們不通過JoinColum來指定外來鍵的名稱,系統會給我們宣告一個名稱)
public ClassRoom getRoom()
{
return room;
}
public void setRoom(ClassRoom room)
{
this.room = room;
}
}
3.多對多
多對多這裡通常有兩種處理方式,一種是通過建立一張中間表,然後由任一一個多的一方來維護關聯關係,另一種就是將多對多拆分成兩個一對多的關聯關係
1. 不使用中間表的實體類
採用中間表的時候由任一一個多的一方來維護關聯關係
Teacher類:
@Entity
@Table(name="t_teacher")
public class Teacher
{
private int id;
private String name;
private Set<Course> courses;
public Teacher()
{
courses = new HashSet<Course>();
}
public void addCourse(Course course)
{
courses.add(course);
}
@Id
@GeneratedValue
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
@ManyToMany(mappedBy="teachers") ---> 表示由Course那一方來進行維護
public Set<Course> getCourses()
{
return courses;
}
public void setCourses(Set<Course> courses)
{
this.courses = courses;
}
}
Course類:
@Entity
@Table(name="t_course")
public class Course
{
private int id;
private String name;
private Set<Teacher> teachers;
public Course()
{
teachers = new HashSet<Teacher>();
}
public void addTeacher(Teacher teacher)
{
teachers.add(teacher);
}
@ManyToMany ---> ManyToMany指定多對多的關聯關係
@JoinTable(name="t_teacher_course", joinColumns={ @JoinColumn(name="cid")},
inverseJoinColumns={ @JoinColumn(name = "tid") }) ---> 因為多對多之間會通過一張中間表來維護兩表直接的關係,所以通過 JoinTable 這個註解來宣告,name就是指定了中間表的名字,JoinColumns是一個 @JoinColumn型別的陣列,表示的是我這方在對方中的外來鍵名稱,我方是Course,所以在對方外來鍵的名稱就是 rid,inverseJoinColumns也是一個 @JoinColumn型別的陣列,表示的是對方在我這放中的外來鍵名稱,對方是Teacher,所以在我方外來鍵的名稱就是 tid
public Set<Teacher> getTeachers()
{
return teachers;
}
public void setTeachers(Set<Teacher> teachers)
{
this.teachers = teachers;
}
@Id
@GeneratedValue
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
2. 採用中間表實體類
把之前的ManyToMany拆分成兩個One-to-Many的對映
Admin類:
@Entity
@Table(name="t_admin")
public class Admin
{
private int id;
private String name;
private Set<AdminRole> ars;
public Admin()
{
ars = new HashSet<AdminRole>();
}
public void add(AdminRole ar)
{
ars.add(ar);
}
@Id
@GeneratedValue
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
@OneToMany(mappedBy="admin") ---> OneToMany關聯到了AdminRole這個類,由AdminRole這個類來維護多對一的關係,mappedBy="admin"
@LazyCollection(LazyCollectionOption.EXTRA)
public Set<AdminRole> getArs()
{
return ars;
}
public void setArs(Set<AdminRole> ars)
{
this.ars = ars;
}
}
Role類:
@Entity
@Table(name="t_role")
public class Role
{
private int id;
private String name;
private Set<AdminRole> ars;
public Role()
{
ars = new HashSet<AdminRole>();
}
public void add(AdminRole ar)
{
ars.add(ar);
}
@Id
@GeneratedValue
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
@OneToMany(mappedBy="role") ---> OneToMany指定了由AdminRole這個類來維護多對一的關聯關係,mappedBy="role"
@LazyCollection(LazyCollectionOption.EXTRA)
public Set<AdminRole> getArs()
{
return ars;
}
public void setArs(Set<AdminRole> ars)
{
this.ars = ars;
}
}
AdminRole類:
@Entity
@Table(name="t_admin_role")
public class AdminRole
{
private int id;
private String name;
private Admin admin;
private Role role;
@Id
@GeneratedValue
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
@ManyToOne ---> ManyToOne關聯到Admin
@JoinColumn(name="aid")
public Admin getAdmin()
{
return admin;
}
public void setAdmin(Admin admin)
{
this.admin = admin;
}
@ManyToOne --->
@JoinColumn(name="rid")
public Role getRole()
{
return role;
}
public void setRole(Role role)
{
this.role = role;
}
}
小技巧:通過hibernate來進行插入操作的時候,不管是一對多、一對一還是多對多,都只需要記住一點,在哪個實體類聲明瞭外來鍵,就由哪個類來維護關係,在儲存資料時,總是先儲存的是沒有維護關聯關係的那一方的資料,後儲存維護了關聯關係的那一方的資料
4.中間表
兩個實體tb_user,tb_role
現在我們再tb_user或者tb_role中任意一個裡面進行維護關係,多對對的情況下我們需要建立一箇中間表來完成這個關係的對映,我們再tb_user中添加註解@ManyToMany然後再新增一個註解@JoinTable因為我們是要建立中間表所以要使用這個註解。JoinTable註解中我們新增如下例子中的內容,joinColumns當前表中的欄位在中間表中的欄位名稱,inverseJoinColumns關聯的外來鍵表在中間表中的欄位名稱
@Entity
@Table(name = "tb_user")
@SequenceGenerator(name = "tb_user_sq",sequenceName = "tb_user_sqe")
public class TbUser extends BaseEntity{
/**
* 使用者名稱
*/
private String userName;
/**
* 登入名
*/
private String loginName;
/**
* 登陸密碼
*/
private String passWord;
/**
* 手機號
*/
private String telPhone;
/**
* 一個使用者可以有多個角色
*/
private List<TbRole> tbRoleList=new ArrayList<>();
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
public String getTelPhone() {
return telPhone;
}
public void setTelPhone(String telPhone) {
this.telPhone = telPhone;
}
@Id
@Override
@GeneratedValue(generator = "tb_user_sq",strategy = GenerationType.SEQUENCE)
public Long getId() {
return this.id;
}
@ManyToMany(cascade = CascadeType.REMOVE,fetch = FetchType.LAZY)
@JoinTable(name = "tb_user_role",joinColumns = @JoinColumn(name="tb_user_id",referencedColumnName = "id"),inverseJoinColumns = @JoinColumn(name = "tb_role_id",referencedColumnName = "id"))
public List<TbRole> getTbRoleList() {
return tbRoleList;
}
public void setTbRoleList(List<TbRole> tbRoleList) {
this.tbRoleList = tbRoleList;
}
}
因為在tb_user中我們維護了兩個表的關係,所以如果我們在tb_role中如果不想建立關聯欄位的話就不用新增tbUser的關係欄位
@Entity
@Table(name = "tb_role")
@SequenceGenerator(name = "tb_role_sq",sequenceName = "tb_role_sqe")
public class TbRole extends BaseEntity{
@Override
@Id
@GeneratedValue(generator = "tb_role_sq",strategy = GenerationType.SEQUENCE)
public Long getId() {
return this.id;
}
private String roleName;
@ManyToMany(mappedBy = "tbRoleList")
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
如果想要在tb_role中進行維護關聯欄位的話如下,我們在欄位中添加註解 @ManyToMany然後使用直接屬性mappedBy值是當前表在關聯表中的欄位名稱
@Entity
@Table(name = "tb_role")
@SequenceGenerator(name = "tb_role_sq",sequenceName = "tb_role_sqe")
public class TbRole extends BaseEntity{
@Override
@Id
@GeneratedValue(generator = "tb_role_sq",strategy = GenerationType.SEQUENCE)
public Long getId() {
return this.id;
}
private String roleName;
private List<TbUser> tbUserList=new ArrayList<>();
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
@ManyToMany(mappedBy = "tbRoleList")
public List<TbUser> getTbUserList() {
return tbUserList;
}
public void setTbUserList(List<TbUser> tbUserList) {
this.tbUserList = tbUserList;
}