SpringData : Spring 的一个子项目。用于简化数据库访问,支持NoSQL 和 关系数据存储。其主要目标是使数据库的访问变得方便快捷。
SpringData 项目所支持 NoSQL 存储:
SpringData 项目所支持的关系数据存储技术:
JPA Spring Data : 致力于减少数据访问层 (DAO) 的开发量, 开发者唯一要做的就只是声明持久层的接口,其他都交给 Spring Data JPA 来帮你完成!
框架怎么可能代替开发者实现业务逻辑呢?比如:当有一个 UserDao.findUserById() 这样一个方法声明,大致应该能判断出这是根据给定条件的 ID 查询出满足条件的 User 对象。Spring Data JPA 做的便是规范方法的名字,根据符合规范的名字来确定方法需要实现什么样的逻辑。
1. Spring Data JPA 进行持久层(即Dao)开发步骤:
声明持久层的接口,该接口继承 Repository(或Repository的子接口,其中定义了一些常用的增删改查,以及分页相关的方法)。
在接口中声明需要的业务方法。Spring Data 将根据给定的策略生成实现代码。
注入service层。
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查询。
<!-- 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相关依赖。
设置连接属性和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(); ... }
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开始。
方法定义规范如下:
简单条件查询:查询某一个实体或者集合
按照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);
事务控制类似于普通的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; } }
有时候我们想复杂的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); }
上面的类图如下:
原文:https://www.cnblogs.com/qlqwjy/p/10722671.html