1.簡介
SpringData : Spring 的一個子項目。用於簡化數據庫訪問,支持NoSQL 和 關系數據存儲。其主要目標是使數據庫的訪問變得方便快捷。
SpringData 項目所支持 NoSQL 存儲:
- MongoDB (文檔數據庫)
- Neo4j(圖形數據庫)
- Redis(鍵/值存儲)
- Hbase(列族數據庫)
SpringData 項目所支持的關系數據存儲技術:
- JDBC
- JPA
JPA Spring Data : 致力於減少數據訪問層 (DAO) 的開發量, 開發者唯一要做的就只是聲明持久層的接口,其他都交給 Spring Data JPA 來幫你完成!
框架怎么可能代替開發者實現業務邏輯呢?比如:當有一個 UserDao.findUserById() 這樣一個方法聲明,大致應該能判斷出這是根據給定條件的 ID 查詢出滿足條件的 User 對象。Spring Data JPA 做的便是規范方法的名字,根據符合規范的名字來確定方法需要實現什么樣的邏輯。
1. Spring Data JPA 進行持久層(即Dao)開發步驟:
聲明持久層的接口,該接口繼承 Repository(或Repository的子接口,其中定義了一些常用的增刪改查,以及分頁相關的方法)。
在接口中聲明需要的業務方法。Spring Data 將根據給定的策略生成實現代碼。
注入service層。
2.JPA、SpringDataJPA、Hibernate關系
JPA是一個規范,Hibernate是一個JPA提供者或實現。Spring Data是Spring Framework的一部分。Spring Data存儲庫抽象的目標是顯著減少為各種持久性存儲實現數據訪問層所需的代碼量。
Spring Data JPA不是JPA提供者。它是一個庫/框架,它在我們的JPA提供程序(如Hibernate)的頂部添加了一個額外的抽象層。
Hibernate是一個JPA實現,而Spring Data JPA是一個JPA數據訪問抽象。Spring Data提供了GenericDao自定義實現的解決方案,它還可以通過方法名稱約定代表您生成JPA查詢。
2.SpringBoot整合SpringDataJPA
1.pom.xml加入依賴
<!-- mysql驅動 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <!-- springdata jpa依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
spring-data-jpa會自動引入hobernate相關依賴。
2.application.properties加入下面配置
設置連接屬性和hibernate顯示語句。
############################################################ # # datasource settings # ############################################################ spring.datasource.driver-class-name= com.mysql.jdbc.Driver spring.datasource.url = jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf-8 spring.datasource.username = root spring.datasource.password = 123456 ############################################################ # # SpringDataJPA相關配置 # ############################################################ # Specify the DBMS #spring.jpa.database = MYSQL # Show or not log for each sql query spring.jpa.showSql=true # Hibernate ddl auto (create, create-drop, update) spring.jpa.hibernate.ddlAuto=update
其自動配置原理解釋:
自動配置的類位於 org.springframework.boot.autoconfigure.orm.jpa 包的JpaProperties類中 :
@ConfigurationProperties(prefix = "spring.jpa") public class JpaProperties { private Map<String, String> properties = new HashMap<String, String>(); private String databasePlatform; private Database database; private boolean generateDdl = false; private boolean showSql = false; private Hibernate hibernate = new Hibernate(); ... }
3.測試
1. 建立實體:(類似於Hibernate注解實體)
package cn.qlq.springData; import java.util.Date; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class User2 { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private String username; private String password; private String userfullname; private Date createtime; private String isdeleted; private String sex; private String address; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username == null ? null : username.trim(); } public String getPassword() { return password; } public void setPassword(String password) { this.password = password == null ? null : password.trim(); } public String getUserfullname() { return userfullname; } public void setUserfullname(String userfullname) { this.userfullname = userfullname == null ? null : userfullname.trim(); } public Date getCreatetime() { return createtime; } public void setCreatetime(Date createtime) { this.createtime = createtime; } public String getIsdeleted() { return isdeleted; } public void setIsdeleted(String isdeleted) { this.isdeleted = isdeleted == null ? null : isdeleted.trim(); } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex == null ? null : sex.trim(); } public String getAddress() { return address; } public void setAddress(String address) { this.address = address == null ? null : address.trim(); } @Override public String toString() { return "User [id=" + id + ", username=" + username + ", password=" + password + ", userfullname=" + userfullname + ", createtime=" + createtime + ", isdeleted=" + isdeleted + ", sex=" + sex + ", address=" + address + "]"; } }
2.編寫dao接口
package cn.qlq.springData; import org.springframework.data.jpa.repository.JpaRepository; public interface UserDao extends JpaRepository<User2, Integer> { }
3.編寫Controller測試代碼,這里省去service層 (基本的增刪改查、分頁查詢)
package cn.qlq.springData; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Example; import org.springframework.data.domain.ExampleMatcher; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("user2") public class UserController2 { @Autowired private UserDao userDao; @RequestMapping("addUser") public String adduser() { for (int i = 0; i < 5; i++) { User2 user2 = new User2(); user2.setAddress("add" + i); user2.setUsername("username" + i); userDao.save(user2); } return "success"; } @RequestMapping("deleteUser") public String deleteUser() { // 批量刪除 // userDao.deleteAll(); userDao.delete(1); return "success"; } @RequestMapping("updateUser") public User2 updateUser() { User2 user = userDao.getOne(4); user.setAddress("修改后地址"); User2 user2 = userDao.saveAndFlush(user); return user2; } @RequestMapping("getCount") public Long getCount() { // 根據條件查詢 // userDao.count(example); long count = userDao.count(); return count; } @RequestMapping("exists") public boolean exists() { // 根據條件判斷 // userDao.exists(Example<S>); boolean exists = userDao.exists(5); return exists; } @RequestMapping("getUser") public User2 getUser() { User2 user = userDao.findOne(2); return user; } @RequestMapping("getUsers") public List<User2> getUsers() { // 查詢所有 // List<User2> findAll = userDao.findAll(); // 根據ID集合查詢 // userDao.findAll(ids); // 根據條件查詢: // Example example = Example.of(user); User2 user = new User2(); user.setAddress("Add"); user.setUsername("user"); ExampleMatcher matcher = ExampleMatcher.matching() .withMatcher("username", ExampleMatcher.GenericPropertyMatchers.contains())// 查詢username包含修改user .withIgnorePaths("address");// 忽略address屬性 Example<User2> example = Example.of(user, matcher); List<User2> users = userDao.findAll(example); return users; } @RequestMapping("getUsersPage") public Page<User2> getUsersPage() { // 根據條件查詢: User2 user = new User2(); user.setAddress("Add"); user.setUsername("user"); ExampleMatcher matcher = ExampleMatcher.matching() .withMatcher("username", ExampleMatcher.GenericPropertyMatchers.contains())// 查詢username包含修改user .withIgnorePaths("address");// 忽略address屬性 // Example example = Example.of(user); Example<User2> example = Example.of(user, matcher); // 構造排序 List<Sort.Order> orders = new ArrayList<Sort.Order>(); orders.add(new Sort.Order(Sort.Direction.DESC, "id")); Sort sort = new Sort(orders); // 構造請求參數,頁號從0開始。 PageRequest pageRequest = new PageRequest(0, 2, sort); // 如果不帶條件不傳第一個參數即可 Page<User2> findAll = userDao.findAll(example, pageRequest); return findAll; } }
上面代碼包含了基本的增刪改查、判斷是否存在、查詢總數,以及分頁查詢,上面所有的批量查詢都可以加上排序以及按照條件Example進行查詢。
分頁封裝類Page類已經對分頁所需的參數進行了封裝,直接使用即可。但是需要注意的是分頁參數第一頁從0開始。
補充:有時候我們希望公共的實體類抽取一些相同的屬性出來,如下:
注意:
1.標注為@MappedSuperclass的類將不是一個完整的實體類,他將不會映射到數據庫表,但是他的屬性都將映射到其子類的數據庫字段中。
2.標注為@MappedSuperclass的類不能再標注@Entity或@Table注解,也無需實現序列化接口。
自增類型ID的抽象實體:
package cn.qs.bean; import java.util.Date; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.MappedSuperclass; /** * 所有實體類中相同的部分(自增ID類型的) * * @author Administrator * */ @MappedSuperclass public abstract class AbstractSequenceEntity { @Id @GeneratedValue(strategy = GenerationType.AUTO) protected Integer id; protected Date createtime; protected String creator; public AbstractSequenceEntity() { this.createtime = new Date(); } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Date getCreatetime() { return createtime; } public void setCreatetime(Date createtime) { this.createtime = createtime; } public String getCreator() { return creator; } public void setCreator(String creator) { this.creator = creator; } }
UUID類型的抽象實體:
package cn.qs.bean; import java.util.Date; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.MappedSuperclass; import org.hibernate.annotations.GenericGenerator; /** * 所有實體類中相同的部分(UUID類型的) * * @author Administrator * */ @MappedSuperclass public abstract class AbstractUUIDEntity { @Id @GeneratedValue(generator = "uuid") @GenericGenerator(name = "uuid", strategy = "uuid") protected String id; protected Date createtime; protected String creator; public AbstractUUIDEntity() { this.createtime = new Date(); } public Date getCreatetime() { return createtime; } public void setCreatetime(Date createtime) { this.createtime = createtime; } public String getCreator() { return creator; } public void setCreator(String creator) { this.creator = creator; } }
基類:
package cn.qs.bean.user; import java.util.Date; import javax.persistence.Entity; import cn.qs.bean.AbstractSequenceEntity; @Entity public class User extends AbstractSequenceEntity { private String username; private String password; private String fullname; private String sex; private String phone; private String email; private Date updatetime; private String roles; private String userblank; public String getUsername() { return username; } public void setUsername(String username) { this.username = username == null ? null : username.trim(); } public String getPassword() { return password; } public void setPassword(String password) { this.password = password == null ? null : password.trim(); } public String getFullname() { return fullname; } public void setFullname(String fullname) { this.fullname = fullname == null ? null : fullname.trim(); } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex == null ? null : sex.trim(); } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone == null ? null : phone.trim(); } public String getEmail() { return email; } public void setEmail(String email) { this.email = email == null ? null : email.trim(); } public Date getUpdatetime() { return updatetime; } public void setUpdatetime(Date updatetime) { this.updatetime = updatetime; } public String getRoles() { return roles; } public void setRoles(String roles) { this.roles = roles == null ? null : roles.trim(); } public String getUserblank() { return userblank; } public void setUserblank(String userblank) { this.userblank = userblank == null ? null : userblank.trim(); } }
補充:JPA執行插入和修改都是Update,JPA對程序調用的save()方法判斷是updata或者insert操作的依據是看實體對象的主鍵是否被賦值。
如果我們傳入的bean帶ID,JPA會先根據ID查詢是否有數據,如果查到數據會執行update語句,如果查不到會執行insert語句;如果不帶ID,會執行insert語句。所以比較智能,有對應ID,會先根據ID去查詢是否存在數據,如果存在就更新;如果不存在就直接插入。
如下:
public void update(FirstCharge firstCharge) { firstChargeMapper.saveAndFlush(firstCharge); }
第一次傳的firstCharge存在ID,且數據庫中有對應的數據,執行的SQL如下:
Hibernate: select firstcharg0_.id as id1_0_0_, firstcharg0_.amount as amount2_0_0_, firstcharg0_.balance as balance3_0_0_, firstcharg0_.gmt_created as gmt_crea4_0_0_, firstcharg0_.grade as grade5_0_0_, firstcharg0_.parent_name as parent_n6_0_0_, firstcharg0_.register_time as register7_0_0_, firstcharg0_.remark as remark8_0_0_, firstcharg0_.second_parent_name as second_p9_0_0_, firstcharg0_.source_id as source_10_0_0_, firstcharg0_.source_name as source_11_0_0_, firstcharg0_.user_id as user_id12_0_0_, firstcharg0_.user_name as user_na13_0_0_ from first_charge firstcharg0_ where firstcharg0_.id=? Hibernate: update first_charge set amount=?, balance=?, gmt_created=?, grade=?, parent_name=?, register_time=?, remark=?, second_parent_name=?, source_id=?, source_name=?, user_id=?, user_name=? where id=?
第二次傳的firstCharge存在ID,數據庫中沒有對應的數據,執行的SQL如下:
Hibernate: select firstcharg0_.id as id1_0_0_, firstcharg0_.amount as amount2_0_0_, firstcharg0_.balance as balance3_0_0_, firstcharg0_.gmt_created as gmt_crea4_0_0_, firstcharg0_.grade as grade5_0_0_, firstcharg0_.parent_name as parent_n6_0_0_, firstcharg0_.register_time as register7_0_0_, firstcharg0_.remark as remark8_0_0_, firstcharg0_.second_parent_name as second_p9_0_0_, firstcharg0_.source_id as source_10_0_0_, firstcharg0_.source_name as source_11_0_0_, firstcharg0_.user_id as user_id12_0_0_, firstcharg0_.user_name as user_na13_0_0_ from first_charge firstcharg0_ where firstcharg0_.id=? Hibernate: insert into first_charge (amount, balance, gmt_created, grade, parent_name, register_time, remark, second_parent_name, source_id, source_name, user_id, user_name) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
JPA執行update的時候會update所有的列,也就是不會更新只有值的列,因此需要我們處理一下:首先查詢一遍,然后將修改的列的值賦值到查詢到的bean上再去修改
如下:
@Override public void update(FirstCharge firstCharge) { FirstCharge systemFirstCharge = firstChargeMapper.findOne(firstCharge.getId()); // 將修改的屬性賦值到系統bean上 BeanUtils.copyProperties(systemFirstCharge, firstCharge); firstChargeMapper.saveAndFlush(systemFirstCharge); }
BeanUtils采用內省將一個bean的屬性賦值到另一個bean上:
package cn.qs.utils; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang3.ArrayUtils; public class BeanUtils { /** * 內省進行數據轉換-javaBean轉map * * @param obj * 需要轉換的bean * @return 轉換完成的map * @throws Exception */ public static <T> Map<String, Object> beanToMap(T obj, boolean putIfNull) throws Exception { Map<String, Object> map = new HashMap<>(); // 獲取javaBean的BeanInfo對象 BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass(), Object.class); // 獲取屬性描述器 PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { // 獲取屬性名 String key = propertyDescriptor.getName(); // 獲取該屬性的值 Method readMethod = propertyDescriptor.getReadMethod(); // 通過反射來調用javaBean定義的getName()方法 Object value = readMethod.invoke(obj); if (value == null && !putIfNull) { continue; } map.put(key, value); } return map; } public static <T> List<Map<String, Object>> beansToMaps(List<T> objs, boolean putIfNull) throws Exception { return beansToMaps(objs, putIfNull, false); } public static <T> List<Map<String, Object>> beansToMaps(List<T> objs, boolean putIfNull, boolean addIndex) throws Exception { List<Map<String, Object>> result = new ArrayList<>(); Map<String, Object> beanToMap = null; int index = 0; for (Object obj : objs) { beanToMap = beanToMap(obj, putIfNull); if (addIndex) { beanToMap.put("index", ++index); } result.add(beanToMap); } return result; } /** * Map轉bean * * @param map * map * @param clz * 被轉換的類字節碼對象 * @return * @throws Exception */ public static <T> T map2Bean(Map<String, Object> map, Class<T> clz) throws Exception { // new 出一個對象 T obj = clz.newInstance(); // 獲取person類的BeanInfo對象 BeanInfo beanInfo = Introspector.getBeanInfo(clz, Object.class); // 獲取屬性描述器 PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { // 獲取屬性名 String key = propertyDescriptor.getName(); Object value = map.get(key); // 通過反射來調用Person的定義的setName()方法 Method writeMethod = propertyDescriptor.getWriteMethod(); writeMethod.invoke(obj, value); } return obj; } public static <T> List<T> maps2Beans(List<Map<String, Object>> maps, Class<T> clz) throws Exception { List<T> result = new ArrayList<>(); for (Map<String, Object> map : maps) { result.add(map2Bean(map, clz)); } return result; } /** * 復制origin的值到dest上 * * @param dest * 目標對象 * @param origin * 元對象 * @param setNull * 如果源對象屬性為null是否覆蓋 * @param excludeFieldNames * 排除的屬性 */ public static <T> void copyProperties(T dest, T origin, boolean setNull, String[] excludeFieldNames) { try { // 獲取person類的BeanInfo對象 BeanInfo destBeanInfo = Introspector.getBeanInfo(dest.getClass(), Object.class); // 獲取目標屬性描述器 PropertyDescriptor[] destBeanInfoPropertyDescriptors = destBeanInfo.getPropertyDescriptors(); for (PropertyDescriptor propertyDescriptor : destBeanInfoPropertyDescriptors) { // 獲取屬性名 String key = propertyDescriptor.getName(); if (ArrayUtils.contains(excludeFieldNames, key)) { continue; } // 獲取該屬性的值 Method readMethod = propertyDescriptor.getReadMethod(); // 如果源對象沒有對應屬性就跳過 Object srcValue = null; try { srcValue = readMethod.invoke(origin); } catch (Exception ignored) { // ignored continue; } // 如果源對象的值null且null不設置的時候跳過 if (srcValue == null && !setNull) { continue; } // 獲取setter方法修改屬性 Method writeMethod = propertyDescriptor.getWriteMethod(); writeMethod.invoke(dest, srcValue); } } catch (Exception ignored) { // ignored } } public static <T> void copyProperties(T dest, T origin) { copyProperties(dest, origin, false, null); } public static <T> Object getProperty(T object, String proeprty) { // 獲取javaBean的BeanInfo對象 BeanInfo beanInfo; try { beanInfo = Introspector.getBeanInfo(object.getClass(), Object.class); } catch (IntrospectionException ignore) { return new Object(); } // 獲取屬性描述器 PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { // 獲取屬性名 String key = propertyDescriptor.getName(); if (proeprty.equals(key)) { // 獲取該屬性的值 Method readMethod = propertyDescriptor.getReadMethod(); // 通過反射來調用javaBean定義的getName()方法 try { Object value = readMethod.invoke(object); return value; } catch (Exception ignore) { return new Object(); } } } return new Object(); } }
根據上述思路封裝的saveOrUpdate方法,如下:
public void saveOrUpdate(FirstCharge firstCharge) { FirstCharge systemFirstCharge = new FirstCharge(); if (firstCharge.getId() != null) { systemFirstCharge = firstChargeMapper.findOne(firstCharge.getId()); } BeanUtils.copyProperties(systemFirstCharge, firstCharge); firstChargeMapper.saveAndFlush(systemFirstCharge); }
三、SpringData方法定義規范
1. 簡單的條件查詢的方法定義規范
方法定義規范如下:
簡單條件查詢:查詢某一個實體或者集合
按照SpringData規范,查詢方法於find|read|get開頭,涉及條件查詢時,條件的屬性用條件關鍵字連接,要注意的是:屬性首字母需要大寫。
支持屬性的級聯查詢;若當前類有符合條件的屬性, 則優先使用, 而不使用級聯屬性。 若需要使用級聯屬性, 則屬性之間使用 _ 進行連接。
支持的關鍵字如下:
Keyword | Sample | JPQL snippet |
---|---|---|
And |
findByLastnameAndFirstname |
… where x.lastname = ?1 and x.firstname = ?2 |
Or |
findByLastnameOrFirstname |
… where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals |
findByFirstname,findByFirstnameIs,findByFirstnameEquals |
… where x.firstname = 1? |
Between |
findByStartDateBetween |
… where x.startDate between 1? and ?2 |
LessThan |
findByAgeLessThan |
… where x.age < ?1 |
LessThanEqual |
findByAgeLessThanEqual |
… where x.age <= ?1 |
GreaterThan |
findByAgeGreaterThan |
… where x.age > ?1 |
GreaterThanEqual |
findByAgeGreaterThanEqual |
… where x.age >= ?1 |
After |
findByStartDateAfter |
… where x.startDate > ?1 |
Before |
findByStartDateBefore |
… where x.startDate < ?1 |
IsNull |
findByAgeIsNull |
… where x.age is null |
IsNotNull,NotNull |
findByAge(Is)NotNull |
… where x.age not null |
Like |
findByFirstnameLike |
… where x.firstname like ?1 |
NotLike |
findByFirstnameNotLike |
… where x.firstname not like ?1 |
StartingWith |
findByFirstnameStartingWith |
… where x.firstname like ?1 (parameter bound with appended % ) |
EndingWith |
findByFirstnameEndingWith |
… where x.firstname like ?1 (parameter bound with prepended % ) |
Containing |
findByFirstnameContaining |
… where x.firstname like ?1 (parameter bound wrapped in % ) |
OrderBy |
findByAgeOrderByLastnameDesc |
… where x.age = ?1 order by x.lastname desc |
Not |
findByLastnameNot |
… where x.lastname <> ?1 |
In |
findByAgeIn(Collection<Age> ages) |
… where x.age in ?1 |
NotIn |
findByAgeNotIn(Collection<Age> age) |
… where x.age not in ?1 |
True |
findByActiveTrue() |
… where x.active = true |
False |
findByActiveFalse() |
… where x.active = false |
IgnoreCase |
findByFirstnameIgnoreCase |
… where UPPER(x.firstame) = UPPER(?1) |
1. 例如:
接口中寫方法根據username和address查詢、按username模糊查詢總數和數據
package cn.qlq.springData; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; public interface UserDao extends JpaRepository<User2, Integer> { // select * from user2 where username=? and address=? User2 findByUsernameAndAddress(String username, String address); List<User2> findByUsernameLike(String username); long countByUsernameLike(String username); }
測試代碼:
@RequestMapping("getUser2ByUsernameAndAddress") public User2 getUser2ByUsernameAndAddress() { return userDao.findByUsernameAndAddress("username1", "add1"); }
結果:
生成的SQL如下:
select user2x0_.id as id1_0_, user2x0_.address as address2_0_, user2x0_.createtime as createti3_0_, user2x0_.isdeleted as isdelete4_0_, user2x0_.password as password5_0_, user2x0_.sex as sex6_0_, user2x0_.userfullname as userfull7_0_, user2x0_.username as username8_0_ from user2 user2x0_ where user2x0_.username=? and user2x0_.address=?
2. 例如:級聯查詢的例子:(根據國家ID查詢人)
(1) 增加1個國家實體類
package cn.qlq.springData; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class Country { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private String countryname; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getCountryname() { return countryname; } public void setCountryname(String countryname) { this.countryname = countryname; } }
(2)修改User2.java
(3)啟動會自動更新表結構,然后構造下面數據:
country表:
user2
(4)接口增加下面方法:
(5)Controller增加代碼:
@RequestMapping("findByCountryId") public List<User2> findByCountryId() { return userDao.findByCountryId(1); }
結果:
3. 查詢方法解析流程
(1)方法參數不帶特殊參數的查詢
假如創建如下的查詢:findByUserDepUuid(),框架在解析該方法時,流程如下:
首先剔除 findBy,然后對剩下的屬性進行解析,假設查詢實體為Doc
先判斷 userDepUuid(根據 POJO 規范,首字母變為小寫)是否為查詢實體的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,繼續往下走
從右往左截取第一個大寫字母開頭的字符串(此處為Uuid),然后檢查剩下的字符串是否為查詢實體的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,則重復這一步,繼續從右往左截取;最后假設 user 為查詢實體的一個屬性
接着處理剩下部分(DepUuid),先判斷 user 所對應的類型是否有depUuid屬性,如果有,則表示該方法最終是根據 "Doc.user.depUuid" 的取值進行查詢;否則繼續按照步驟3的規則從右往左截取,最終表示根據 "Doc.user.dep.uuid" 的值進行查詢。
可能會存在一種特殊情況,比如 Doc包含一個 user 的屬性,也有一個 userDep 屬性,此時會存在混淆。可以明確在級聯的屬性之間加上 "_" 以顯式表達意圖,比如 "findByUser_DepUuid()" 或者 "findByUserDep_uuid()"。
(2) 方法參數帶特殊參數的查詢
特殊的參數: 還可以直接在方法的參數上加入分頁或排序的參數,比如:
Page<User2> findByName(String name, Pageable pageable)
List<User2> findByName(String name, Sort sort);
四、事務以及自定義SQL查詢
1.事務
事務控制類似於普通的web開發在service層加入@Transactional注解即可。而且一般事務控制在service層控制。
package cn.qlq.springData; import javax.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service @Transactional public class User2ServiceImpl implements User2Service { @Autowired private UserDao userDao; @Override public void save(User2 user2) { userDao.save(user2); int jj = 1 / 0; } }
2.自定義SQL查詢
有時候我們想復雜的SQL通過自定義sql查詢,一般有下面幾種方式:
(1)用JPQL查詢
專業的術語稱為jpql,其用法類似hibernate的hql
索引參數方式獲取: 索引值從1開始,查詢中'?x'的個數要和方法的參數個數一致,且順序也要一致
命名參數方式: 可以用':參數名'的形式,在方法參數中使用@Param("參數名")注解,這樣就可以不用按順序來定義形參
自定義的Query查詢中jpql語句有like查詢時,可以直接把%號寫在參數的前后,這樣傳參數就不用把%號拼接進去了。
例如:
@Query("from User2 where username = ?1 and address = ?2 or address like %?2%") List<User2> getUserByCondition(String name, String address); @Query("from User2 where username like %:name%") List<User2> getUserByUsername(@Param("name") String name);
(2)使用原生的SQL進行查詢
編寫查詢的SQL語句,並且在@Query注解中設置nativeQuery=true。
@Query(value = "select * from user2 where username like %:name%", nativeQuery = true) List<User2> getUsersByUsername(@Param("name") String username);
唯一的不足是沒有找到辦法自定義SQL查詢可以返回Map和接收Map參數。這個可以采用集成mybatis解決。
五、接口解釋
1.Repository 是一個空接口,即是一個標記接口。源碼
public interface Repository<T, ID extends Serializable> { }
2.若我們定義的接口實現了 Repository,則該接口會被 IOC 容器識別為一個 Repository Bean,納入到 IOC 容器中,進而可以在該接口中定義一些符合規范的方法。最后容器啟動的時候會對相應的接口生成代理對象。
3.還可以通過 @RepositoryDefinition 注解來替代繼承 Repository 接口。(但是不具備繼承接口通用的功能)
4.Repository 接口的實現類:
1)CrudRepository: 繼承 Repository,實現了一組 CRUD 相關的方法
2)PagingAndSortingRepository: 繼承 CrudRepository,實現了一組分頁排序相關的方法(但是不具備條件篩選功能)
3)JpaRepository繼承PagingAndSortingRepository接口和QueryByExampleExecutor接口(繼承QueryByExampleExecutor接口,可以實現條件查詢 )
4)自定義的 XxxxRepository 需要繼承 JpaRepository ,這樣的 XxxxRepository 接口就具備了通用的數據訪問控制層的能力。
注: QueryByExampleExecutor: 不屬於Repository體系,實現一組 JPA條件查詢相關的方法
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.data.repository.query; import org.springframework.data.domain.Example; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; public interface QueryByExampleExecutor<T> { <S extends T> S findOne(Example<S> var1); <S extends T> Iterable<S> findAll(Example<S> var1); <S extends T> Iterable<S> findAll(Example<S> var1, Sort var2); <S extends T> Page<S> findAll(Example<S> var1, Pageable var2); <S extends T> long count(Example<S> var1); <S extends T> boolean exists(Example<S> var1); }
上面的類圖如下:
注意:
SpringDataJPA修改的時候是不會修改有值的列,沒有類似於mybatis的updateByPrimaryKeySelective功能,自己在修改數據的時候要特別注意。
補充:如果自己只是想擴展一下JPA接口,不想生成對應的實例,如下:
package cn.qs.mapper; import java.io.Serializable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.repository.NoRepositoryBean; /** * 通用的CRUDMapper接口 * * @author Administrator * * @param <T> * @param <E> */ @NoRepositoryBean public interface BaseCRUDMapper<T, E extends Serializable> extends JpaRepository<T, E> { }
注意:
必須加NoRepositoryBean 注解,否則spring會生成對應的接口實現而報錯。
補充:SpringData JPA 在解析實體類字段時駝峰自動添加下划線問題
#列名為駝峰無修改命名 spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl #遇到大寫字母 加_的命名 #spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
補充:springdataJPA創建聯合索引主鍵表方法如下:
大致分為下面兩步
1.創建主鍵類:(類名一般遵循規范起名為PK)
package com.zd.bx.bean.user; import java.io.Serializable; public class UserRolePK implements Serializable { /** * */ private static final long serialVersionUID = -766021544365439860L; /** * 用戶ID */ private Long userId; /** * 角色ID */ private Long roleId; public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } public Long getRoleId() { return roleId; } public void setRoleId(Long roleId) { this.roleId = roleId; } }
2.創建類並聲明聯合主鍵(equals和hashCode方法要重寫)
package com.zd.bx.bean.user; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.IdClass; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; @Entity @IdClass(UserRolePK.class) public class UserRole { /** * 用戶ID */ @Id private Long userId; /** * 角色ID */ @Id private Long roleId; public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } public Long getRoleId() { return roleId; } public void setRoleId(Long roleId) { this.roleId = roleId; } @Override public int hashCode() { return new HashCodeBuilder().append(userId).append(roleId).toHashCode(); } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (obj == this) { return true; } if (!(obj instanceof UserRole)) { return false; } final UserRole tmpUserRole = (UserRole) obj; return new EqualsBuilder().append(userId, tmpUserRole.getUserId()).append(roleId, tmpUserRole.getRoleId()) .isEquals(); } }
補充:用springdata生成索引的方法如下:
1. 字段上加注解(使用的是hibernate的注解)
import org.hibernate.annotations.Index; /** * 創建者 */ @Index(name = "creator") @TableField(update = "%s") protected String creator;
2.上面的注解會提示過時了,參考:javax.persistence包下的index注解
@Table(indexes = { @Index(name = "type", columnList = "type") }) public class Dictionary extends AbstractSequenceEntity {
...
查看Table注解源碼: 用到了靜態導包。
/** <a href="http://www.cpupk.com/decompiler">Eclipse Class Decompiler</a> plugin, Copyright (c) 2017 Chen Chao. */ package javax.persistence; import java.lang.annotation.Target; import java.lang.annotation.Retention; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Specifies the primary table for the annotated entity. Additional * tables may be specified using {@link SecondaryTable} or {@link * SecondaryTables} annotation. * * <p> If no <code>Table</code> annotation is specified for an entity * class, the default values apply. * * <pre> * Example: * * @Entity * @Table(name="CUST", schema="RECORDS") * public class Customer { ... } * </pre> * * @since 1.0 */ @Target(TYPE) @Retention(RUNTIME) public @interface Table { /** * (Optional) The name of the table. * <p> Defaults to the entity name. */ String name() default ""; /** (Optional) The catalog of the table. * <p> Defaults to the default catalog. */ String catalog() default ""; /** (Optional) The schema of the table. * <p> Defaults to the default schema for user. */ String schema() default ""; /** * (Optional) Unique constraints that are to be placed on * the table. These are only used if table generation is in * effect. These constraints apply in addition to any constraints * specified by the <code>Column</code> and <code>JoinColumn</code> * annotations and constraints entailed by primary key mappings. * <p> Defaults to no additional constraints. */ UniqueConstraint[] uniqueConstraints() default {}; /** * (Optional) Indexes for the table. These are only used if * table generation is in effect. Note that it is not necessary * to specify an index for a primary key, as the primary key * index will be created automatically. * * @since 2.1 */ Index[] indexes() default {}; }
javax.persistence包下的index源碼如下,Target屬性為空:
/** <a href="http://www.cpupk.com/decompiler">Eclipse Class Decompiler</a> plugin, Copyright (c) 2017 Chen Chao. */ package javax.persistence; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Retention; import java.lang.annotation.Target; /** * Used in schema generation to specify creation of an index. * <p> * Note that it is not necessary to specify an index for a primary key, * as the primary key index will be created automatically. * * <p> * The syntax of the <code>columnList</code> element is a * <code>column_list</code>, as follows: * * <pre> * column::= index_column [,index_column]* * index_column::= column_name [ASC | DESC] * </pre> * * <p> If <code>ASC</code> or <code>DESC</code> is not specified, * <code>ASC</code> (ascending order) is assumed. * * @see Table * @see SecondaryTable * @see CollectionTable * @see JoinTable * @see TableGenerator * * @since 2.1 * */ @Target({}) @Retention(RUNTIME) public @interface Index { /** * (Optional) The name of the index; defaults to a provider-generated name. */ String name() default ""; /** * (Required) The names of the columns to be included in the index, * in order. */ String columnList(); /** * (Optional) Whether the index is unique. */ boolean unique() default false; }
補充:基於SpringdataJPA的通用Mapper與Service、Controller的代碼如下:
參考:https://github.com/qiao-zhi/SSMTemplate.git