spring data jpa
spring data是spring的一个子项目,在官网就可以发现,springdata和springboot,springcloud等是一个级别的项目
springdata主要是做数据层的工作,包含了Spring Data Commons,Spring Data JPA,Spring Data MongoDB,Spring Data Redis等模块,其中Spring Data Commons是公共部分,Spring Data JPA,Spring Data MongoDB,Spring Data Redis等是具体的数据操作实现,Spring Data JPA是对关系型数据库的操作,Spring Data MongoDB是对mongodb的操作,Spring Data Redis是对redis的操作
Spring Data Commons之Repository接口
Repository接口是springdate操作db的最顶级接口,ctrl+h调出层级关系
ReactiveCrudRepository:响应式编程,主要支持当前 NoSQL 方面的操作
RxJava2CrudRepository:为了支持 RxJava 2 做的标准响应式编程的接口
CoroutineCrudRepository :为了支持 Kotlin 语法而实现的
CrudRepository :主要就是jpa相关的操作接口
Repository:没有任何方法。
CrudRepository:顾名思义,包含了简单的crud方法
PagingAndSortingRepository:顾名思义,包含了简单的分页排序方法,因为也继承了CrudRepository接口,所以也有简单的crud方法
QueryByExampleExecutor:顾名思义,包含了简单的Example查询方法
JpaRepository:jpa的拓展方法,拥有PagingAndSortingRepository接口和QueryByExampleExecutor接口拥有的方法
SimpleJpaRepository:jpa接口的默认实现类
Spring Data JPA是利用方法名来进行crud操作的
@Entity
@Data
public class Users {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Long userid;
private String username;
private Integer age;
private String address;
}
@Repository
public interface UserDao extends JpaRepository<Users,Long> {
Users findUsersByUsername(String username);//通过username查询user
}
语法:查询策略(关键字)+ 查询字段 + 一些限制性条件组成
在PartTree里面可以发现是通过正则表达式匹配的
在枚举Type里面发现,限制条件是通过枚举类事先弄好的,不是随意写的
分页排序
分页:Pageable
排序:Sort
Page<Users> findByUsername(String username, Pageable pageable);
Slice<Users> findByAddress(String address, Pageable pageable);
List<Users> findByUsername(String username, Sort sort);
List<Users> findByAge(Integer age, Pageable pageable);
查询实体其中几个字段
定义一个dto
如果只想查询某个实体的其中几个字段,可以直接定义dto的返回类型
@Data
@AllArgsConstructor//全参的构造方法很重要
public class UserDto {
private String username;
}
@Repository
public interface UserDao extends JpaRepository<Users,Long> {
UserDto findByUsername(String username);
}
这样就可以直接拿username的数据了,这个只能做查询,不能做更新操作
还可以使用接口定义
public interface UserOnlyUsername {
String getUsername();
}
@Repository
public interface UserDao extends JpaRepository<Users,Long> {
UserOnlyUsername findTopByUsername(String username);
}
这样也可以直接获取username数据
@Query用法
@Query使用的是JPQL语法,JPQL类似于SQL语法,不过JPQL操作的是实体对象
@Repository
public interface UserDao extends JpaRepository<Users,Long> {
/***
* query查询
*/
@Query(value = "from Users where username=:username")
Users findByUsername(@Param("username") String username);//@Param可以不用,不使用就按个按照顺序对应到JPQL的参数里面,使用了就可以不按照顺序,底层是按照名称查找
}
1、Like查询
@Repository
public interface UserDao extends JpaRepository<Users,Long> {
@Query(value = "select u from Users u where u.username like concat('%',?1,'%')")
List<Users> findByUsernameLike(String username);
}
我在最开始写的是@Query(value = "select Users from Users where username like concat('%',?1,'%')")
报错了,后面就加了一个别名就好了,我猜测是不是不使用别名的话默认select后面跟的是字段,而没有Users的字段所以启动报错了
2、使用原始sql查询
使用nativeQuery = true,表示使用原始的sql
@Repository
public interface UserDao extends JpaRepository<Users,Long> {
@Query(value = "select * from users where username = :username", nativeQuery = true)
Users findByUsername(@Param("username") String username);
}
如果使用原始的sql的话,不能直接加加排序
@Repository
public interface UserDao extends JpaRepository<Users,Long> {
//这是错误的做法
@Query(value = "select * from users where username = :username", nativeQuery = true)
Users findByUsername(@Param("username") String username,Sort sort);//不能直接在后面加sort排序
}
要把sort当做参数一样传递进去,所以需要加sql里面加上order by
@Repository
public interface UserDao extends JpaRepository<Users,Long> {
//这是正确的做法
@Query(value = "select * from users where username = :username order by :sort", nativeQuery = true)
Users findByUsername(@Param("username") String username,String sort);
}
返回指定dto
@Entity
@Data
public class UsersExtend {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
private Long userid;
private String card;
private Double height;
private Double weight;
}
@Entity
@Data
public class Users {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Long userid;
private String username;
private Integer age;
private String address;
}
一张用户表,一张用户信息拓展表,我们现在要返回username,address,card信息
1、直接查询想要的字段
@Repository
public interface UserDao extends JpaRepository<Users,Long> {
@Query(value = "select u.username,u.address,e.card from Users u ,UsersExtend e where u.userid = e.userid and u.username = :username" )
List<Object> findUsersByUsername(@Param("username")String username);
}
这样不好的地方在于返回的是List<Object>
,而Object
肯定不是我们想要的东西,还得拿出来在赋值给新定义的dto,很麻烦
2、使用dto直接赋值
@Data
@AllArgsConstructor
public class UsersDto {
private String username;
private String address;
private String card;
}
@Repository
public interface UserDao extends JpaRepository<Users,Long> {
@Query(value = "select new com.zhu.jpa.pojo.UsersDto(u.username,u.address,e.card) from Users u ,UsersExtend e where u.userid = e.userid and u.username = :username")
List<UsersDto> findUsersByUsernameByUsersDto(@Param("username")String username);
}
3、使用接口返回
public interface UsersDtoView {
String getUsername();
String getAddress();
String getCard();
}
@Repository
public interface UserDao extends JpaRepository<Users,Long> {
@Query(value = "select u.username as username,u.address as address,e.card as card from Users u ,UsersExtend e where u.userid = e.userid and u.username = :username")
List<UsersDtoView> findUsersByUsernameByUsersDtoView(@Param("username")String username);
}
这样返回的结果是只读的,职责单一,是比较好的
在前面我们使用了一个concat关键字做字符串的拼接,在ParameterizedFunctionExpression中还可以看到其他可以使用的关键字
public static final List<String> STANDARD_JPA_FUNCTION_NAMES = Arrays.asList("CONCAT", "SUBSTRING", "TRIM", "UPPER", "LOWER", "LOCATE", "LENGTH", "ABS", "SQRT", "MOD", "SIZE", "INDEX", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP");
@Query动态查询
public interface UsersExtendDao extends JpaRepository<UsersExtend,Long> {
//使用JPQL
@Query(value = "select e from UsersExtend e where (:card is null or e.card = :card) and (:weight is null or e.weight = :weight)")
List<UsersExtend> findByCardAndAndWeight(String card, Double weight);
//使用原始sql
@Query(value = "select e.* from users_extend e where (:card is null or e.card = :card) and (:weight is null or e.weight = :weight)",nativeQuery = true)
List<UsersExtend> findByCardAndWeight(String card, Double weight);
}
如果想查看执行的sql和参数,可以配置日志
#打印sql
spring.jpa.show-sql=true
#打印参数
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=trace
QueryByExampleExecutor
public interface QueryByExampleExecutor<T> {
<S extends T> Optional<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);
}
JpaRepository继承了QueryByExampleExecutor接口,所以JpaRepository也拥有了QueryByExampleExecutor的全部方法
public List<Users> findUsersByExample(String username){
Users probe = new Users();
probe.setUsername("xiao");
ExampleMatcher matcher =ExampleMatcher.matchingAll().withMatcher("username",ExampleMatcher.GenericPropertyMatchers.startsWith());
Example example =Example.of(probe,matcher);
List<Users> all = userDao.findAll(example);
return all;
}
JpaSpecificationExecutor
public interface JpaSpecificationExecutor<T> {
Optional<T> findOne(@Nullable Specification<T> var1);
List<T> findAll(@Nullable Specification<T> var1);
Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
List<T> findAll(@Nullable Specification<T> var1, Sort var2);
long count(@Nullable Specification<T> var1);
}
EntityManager
JPA规定,操作数据库实体话必须通过EntityManager 操作,我们知道spring data jpa的实现类是SimpleJpaRepository,而SimpleJpaRepository在底层操作也是EntityManager 实现的,如下,进行删除操作的时候
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
private final EntityManager em;
public SimpleJpaRepository(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
this.escapeCharacter = EscapeCharacter.DEFAULT;
Assert.notNull(entityInformation, "JpaEntityInformation must not be null!");
Assert.notNull(entityManager, "EntityManager must not be null!");
this.entityInformation = entityInformation;
this.em = entityManager;
this.provider = PersistenceProvider.fromEntityManager(entityManager);
}
@Transactional
public void delete(T entity) {
Assert.notNull(entity, "Entity must not be null!");
if (!this.entityInformation.isNew(entity)) {
Class<?> type = ProxyUtils.getUserClass(entity);
T existing = this.em.find(type, this.entityInformation.getId(entity));
if (existing != null) {
this.em.remove(this.em.contains(entity) ? entity : this.em.merge(entity));
}
}
}
}
EntityManager 比较常用的方法
public interface EntityManager {
//用于将新创建的Entity纳入EntityManager的管理。该方法执行后,传入persist()方法的 Entity 对象转换成持久化状态。
public void persist(Object entity);
//将游离态的实体merge到当前的persistence context里面,一般用于更新。
public <T> T merge(T entity);
//将实体对象删除,物理删除
public void remove(Object entity);
//将当前的persistence context中的实体,同步到数据库里面,只有执行了这个方法,上面的EntityManager的操作才会DB生效;
public void flush();
//根据实体类型和主键查询一个实体对象;
public <T> T find(Class<T> entityClass, Object primaryKey);
//根据JPQL创建一个Query对象
public Query createQuery(String qlString);
//利用CriteriaUpdate创建更新查询
public Query createQuery(CriteriaUpdate updateQuery);
//利用原生的sql语句创建查询,可以是查询、更新、删除等sql
public Query createNativeQuery(String sqlString);
}
如果想自己定义数据库操作可以使用EntityManager ,可以通过@PersistenceContext注解将容器管理的EntityManager注入进来
@PersistenceContext
private EntityManager entityManager;
自定义repository
我们知道JpaRepository里面delete方法是物理删除,如果需要改成逻辑删除那么就需要我们重新定义删除方法
1、定义一个接口CustomizedUserRepository
public interface CustomizedUserRepository {
void deleteUsers(Users users);
}
实现类
public class CustomizedUserRepositoryImpl implements CustomizedUserRepository{
private EntityManager entityManager;
public CustomizedUserRepositoryImpl(EntityManager entityManager) {
this.entityManager = entityManager;
}
@Transactional
@Override
public void deleteUsers(Users users) {
//还没有定义逻辑删除字段,用address代替一下
users.setAddress("逻辑删除");
entityManager.merge(users);
entityManager.flush();
}
}
//userdao继承定义好的接口
public interface UserDao extends JpaRepository<Users,Long>,CustomizedUserRepository
//在service里面可以直接调用
public void deleteUsers(String username){
Optional<Users> optional = userDao.findById(1L);
Users users = optional.get();
System.out.println(users);
userDao.deleteUsers(users);
}
修改成功
2、上面这种方法就是要使用自己定义的deleteUsers方法,现在我们想直接覆盖原来的delete方法,也是可以行的,可以通过@EnableJpaRepositories 指定 repositoryBaseClass
@SpringBootApplication
@EnableJpaRepositories(repositoryBaseClass = CustomizedUsersRepository.class)
public class JpaApplication {
public static void main(String[] args) {
SpringApplication.run(JpaApplication.class, args);
}
}
新建CustomizedUsersRepository类继承SimpleJpaRepository
@Transactional(readOnly = true)
public class CustomizedUsersRepository<T,ID> extends SimpleJpaRepository<T, ID>{
private final JpaEntityInformation<T, ?> entityInformation;
private final EntityManager em;
public CustomizedUsersRepository(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
this.entityInformation = entityInformation;
this.em = entityManager;
}
public CustomizedUsersRepository(Class<T> domainClass, EntityManager em) {
super(domainClass, em);
entityInformation = null;
this.em = em;
}
@Transactional
@Override
public void delete(T entity) {
//如果是逻辑删除的话,我们的实体应该有一个基类,你面包含了公共的字段,如果是否删除等
//然后就可以在这里进行修改字段值达到更新效果
System.out.println(entity);
}
}