springboot整合springdata-jpa
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