概述
JPA 应该都熟悉了,我就不多说了什么是JPA了。目前JPA主要实现由hibernate和openJPA等。
Spring Data JPA 是Spring Data 的一个子项目,它通过提供基于JPA的Repository极大了减少了操作JPA的代码。笔者觉得这个由SpringBoot 提供的JPARepository真的是非常爽。基本上大部分的业务都可以满足了。
在Spring环境中需要配置大量了XML配置,但是SpringBoot基本上帮助我们配置好了,我们只需要简单地配置一下DataSource和几项jpa额外的配置就完成了整个配置持久化层的工作。EntityManagerFactory 那些都不用配置了。
常规贴出一下Spring-data-JPA的MAVEN
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
一、数据源和JPA配置
当然我们还是需要在application.properties配置数据源,SpringBoot默认是使用tomcat的数据源(intellij有自动提示,spring.datasource.tomcat开头的配置):
#datasource spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_db?charset=utf8mb4&serverTimezone=UTC spring.datasource.username=root spring.datasource.password=tonyyan spring.datasource.tomcat.max-active=20 spring.datasource.tomcat.test-while-idle=true spring.datasource.tomcat.validation-query=select 1 spring.datasource.tomcat.default-auto-commit=false spring.datasource.tomcat.min-idle=15 spring.datasource.tomcat.initial-size=15最后我们做一些JPA的配置,spring.jpa开头的配置
spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true spring.jackson.serialization.indent-output=true当然SpringBoot的maven也没有添加mysql driver的依赖。所以要吃自己:
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>6.0.6</version> </dependency>
二、Entity配置
开始之前我先提供一下我的entity配置,便于理解后面的查询操作,我们使用模仿微博的数据实体:
User类:
@Entity @Table(name = "users") public class User { public User() { } public User(String username, String userpwd) { this.username = username; this.userpwd = userpwd; } private long userId; private String username; private String userpwd; @JsonIgnore private Set<Weibo> weibos; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) public long getUserId() { return userId; } public void setUserId(long userId) { this.userId = userId; } @Column(name="username") public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } @Column(name = "userpwd") public String getUserpwd() { return userpwd; } public void setUserpwd(String userpwd) { this.userpwd = userpwd; } @OneToMany(fetch = FetchType.LAZY,cascade = {CascadeType.REMOVE},mappedBy = "user") public Set<Weibo> getWeibos() { return weibos; } public void setWeibos(Set<Weibo> weibos) { this.weibos = weibos; } @Override public String toString() { return "User{" + "userId=" + userId + ", username='" + username + ''' + ", userpwd='" + userpwd + ''' + '}'; } }
Weibo类:
@Entity @Table(name="weibo") public class Weibo { public Weibo() { } public Weibo(User user, String weiboText, Date createDate) { this.user = user; this.weiboText = weiboText; this.createDate = createDate; } private long weiboId; private User user; private String weiboText; private Date createDate; private Set<Comment> comments; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) public long getWeiboId() { return weiboId; } public void setWeiboId(long weiboId) { this.weiboId = weiboId; } @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "user_id") public User getUser() { return user; } public void setUser(User user) { this.user = user; } @Column(name = "weibo_text") public String getWeiboText() { return weiboText; } public void setWeiboText(String weiboText) { this.weiboText = weiboText; } @Column(name = "create_date") public Date getCreateDate() { return createDate; } public void setCreateDate(Date createDate) { this.createDate = createDate; } @OneToMany(fetch = FetchType.LAZY,cascade = {CascadeType.REMOVE},mappedBy = "weibo") public Set<Comment> getComments() { return comments; } public void setComments(Set<Comment> comments) { this.comments = comments; } @Override public String toString() { return "Weibo{" + "weiboId=" + weiboId + ", user=" + user + ", weiboText='" + weiboText + ''' + ", createDate=" + createDate + '}'; } }
comment评论类:
@Entity @Table(name = "comments") public class Comment { public Comment() { } public Comment(User user, Weibo weibo, String commentText, Date commentDate) { this.user = user; this.weibo = weibo; this.commentText = commentText; this.commentDate = commentDate; } private long commentId; private User user; @JsonIgnore private Weibo weibo; private String commentText; private Date commentDate; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) public long getCommentId() { return commentId; } public void setCommentId(long commentId) { this.commentId = commentId; } @ManyToOne @JoinColumn(name = "user_id") public User getUser() { return user; } public void setUser(User user) { this.user = user; } @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "weibo_id") public Weibo getWeibo() { return weibo; } public void setWeibo(Weibo weibo) { this.weibo = weibo; } @Column(name = "comment_text") public String getCommentText() { return commentText; } public void setCommentText(String commentText) { this.commentText = commentText; } @Column(name = "comment_date") public Date getCommentDate() { return commentDate; } public void setCommentDate(Date commentDate) { this.commentDate = commentDate; } @Override public String toString() { return "Comment{" + "commentId=" + commentId + ", user=" + user + ", weibo=" + weibo + ", commentText='" + commentText + ''' + ", commentDate=" + commentDate + '}'; } }
三、强大的Spring JpaRepository
SpringBoot创建DAO层很多种方法其中japrepository是最强大的而且最有特色的一种,我们可以针对不同的实体创建repository接口。Spring会根据方法名称的规则进行自动生成实现,强大的不要不要的。在SpringBoot中默认已经提供了非常多的常规CRUD操作的repository,以下就是Spring为我们提供的repository接口:
@NoRepositoryBean public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> { List<T> findAll(); List<T> findAll(Sort var1); List<T> findAll(Iterable<ID> var1); <S extends T> List<S> save(Iterable<S> var1); void flush(); <S extends T> S saveAndFlush(S var1); void deleteInBatch(Iterable<T> var1); void deleteAllInBatch(); T getOne(ID var1); <S extends T> List<S> findAll(Example<S> var1); <S extends T> List<S> findAll(Example<S> var1, Sort var2); }
@NoRepositoryBean public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> { Iterable<T> findAll(Sort var1); Page<T> findAll(Pageable var1); }
@NoRepositoryBean public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> { <S extends T> S save(S var1); <S extends T> Iterable<S> save(Iterable<S> var1); T findOne(ID var1); boolean exists(ID var1); Iterable<T> findAll(); Iterable<T> findAll(Iterable<ID> var1); long count(); void delete(ID var1); void delete(T var1); void delete(Iterable<? extends T> var1); void deleteAll(); }
这些操作完全不用我们去实现,这些不是我们以往在普通的Spring项目中自己所定义的BaseDao吗?SpringBoot真的是非常体贴,大大减低了我们的工作量。但是更为强大的还在后面。我们通过继承JpaRepository接口,除了可以获得上面的基础CRUD操作方法之外,还可以通过Spring规定的接口命名方法自动创建复杂的CRUD操作,以下是我在Spring Data JPA 文档中找到的命名规则表:
Keyword | Sample | JPQL snippet |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public interface UserRepository extends JpaRepository<User,Long> { //查询用户名称包含username字符串的用户对象 List<User> findByUsernameContaining(String username); //获得单个用户对象,根据username和pwd的字段匹配 User getByUsernameIsAndUserpwdIs(String username,String pwd); //精确匹配username的用户对象 User getByUsernameIs(String username); }
什么annotation都不用打,也不需要写实现。直接使用即可:
@Controller public class TestController { @Autowired private UserRepository userRepository; @RequestMapping("/searchUser/{username}") public @ResponseBody List<User> searchUser(@PathVariable("username") String username) { List<User> result = this.userRepository.findByUsernameContaining(username); return result; } }
方便到不可相信,如果在intellij还可以有提示,根本不需要记住命名规则,只要你懂点英文基本上intellij给你的提示就足够了。
Spring提供了非常大的自由度给开发者,我们可以在接口方法中通过定义@Query annotation自定义接口方法的JPQL语句,其实笔者对JPQL并不熟悉,但是笔者对HQL非常熟悉,其实笔者感觉两者基本上并没有什么差别,笔者在JPA开发过程当中直接都是在写JPQH的语句当中使用HQL也正常无误。以下是WeiboRepository的接口:
public interface WeiboRepository extends JpaRepository<Weibo,Long>,JpaSpecificationExecutor<Weibo> { @Query("select w from Weibo w where w.user.username = :username") List<Weibo> searchUserWeibo(@Param("username") String username); @Query("select w from Weibo w where w.user.username = :username") List<Weibo> searchUserWeibo(@Param("username") String username, Sort sort); @Modifying @Transactional(readOnly = false) @Query("update Weibo w set w.weiboText = :text where w.user = :user") int setUserWeiboContent(@Param("text")String weiboText,@Param("user")User user);
Page<Weibo> findByUserIsAndWeiboTextContaining(User user, String weiboText, Pageable pageable);@Transactional ( readOnly = false ) int deleteByUser (User user) ; }
除了我们刚刚说过的@Query annotation之外,我这里还是用了Spring Data JPA提供的Sort和Pageable。暂时忽略JpaSpecificationExecutor<Weibo>接口的继承
Sort提供字段排序的功能,而Pageable则提供分页的功能。需要注意的是,如果使用了Pageable返回值是Page<T>的泛型对象,这个对象里面提供的分页信息相当丰富。足够使用。只要方法有Pageable参数就会自动实现分页,同时将总页面数和总对象数等信息都通过page对象一一返回。
以下是searchUserWeibo方法、findByUserIsAndWeiboTextLike方法的使用:
@RequestMapping("/username/{username}") public List<Weibo> getUserWeibo(@PathVariable("username") String username) { return this.weiboRepository.searchUserWeibo(username,new Sort(new Sort.Order(Sort.Direction.DESC,"weiboId"))); }Sort的使用非常简单,可以看到上述代码当中可以使用多个order进行排序。
@RequestMapping("/simpleSearch") public Page<Weibo> simpleSearch(String username,String weiboText,int pageNo,int pageSize){ User user = this.userRepository.getByUsernameIs(username); return this.weiboRepository.findByUserIsAndWeiboTextContaining(user,weiboText,new PageRequest(pageNo,pageSize)); }Pageable是一个接口,而上述使用到的PageRequest是一个实现类。这里的pageNo是从0开始。Page<Weibo>的数据结构如下,以下是返回的Json字符串:
- {
- "content": [
- {
- "weiboId": 4,
- "user": {
- "userId": 1,
- "username": "TONY",
- "userpwd": "TONYYAN"
- },
- "weiboText": "TONY's WEIBO_D!!!",
- "createDate": 1498751667000,
- "comments": []
- },
- {
- "weiboId": 5,
- "user": {
- "userId": 1,
- "username": "TONY",
- "userpwd": "TONYYAN"
- },
- "weiboText": "TONY's WEIBO_E!!!",
- "createDate": 1498751667000,
- "comments": []
- }
- ],
- "last": true,
- "totalElements": 5,
- "totalPages": 2,
- "number": 1,
- "size": 3,
- "sort": null,
- "first": false,
- "numberOfElements": 2
- }
或者你可能会发现了如下的接口方法:
@Transactional(readOnly = false) int deleteByUser(User user);这个也是Spring提供的命名实现,我是根据intellij的提示打出来的,但是这个会发出很多条SQL:
Hibernate: select user0_.user_id as user_id1_2_, user0_.username as username2_2_, user0_.userpwd as userpwd3_2_ from users user0_ where user0_.username=?
Hibernate: select weibo0_.weibo_id as weibo_id1_3_, weibo0_.create_date as create_d2_3_, weibo0_.user_id as user_id4_3_, weibo0_.weibo_text as weibo_te3_3_ from weibo weibo0_ left outer join users user1_ on weibo0_.user_id=user1_.user_id where user1_.user_id=?
Hibernate: select comments0_.weibo_id as weibo_id5_0_0_, comments0_.comment_id as comment_1_0_0_, comments0_.comment_id as comment_1_0_1_, comments0_.comment_date as comment_2_0_1_, comments0_.comment_text as comment_3_0_1_, comments0_.user_id as user_id4_0_1_, comments0_.weibo_id as weibo_id5_0_1_, user1_.user_id as user_id1_2_2_, user1_.username as username2_2_2_, user1_.userpwd as userpwd3_2_2_ from comments comments0_ left outer join users user1_ on comments0_.user_id=user1_.user_id where comments0_.weibo_id=?
Hibernate: select comments0_.weibo_id as weibo_id5_0_0_, comments0_.comment_id as comment_1_0_0_, comments0_.comment_id as comment_1_0_1_, comments0_.comment_date as comment_2_0_1_, comments0_.comment_text as comment_3_0_1_, comments0_.user_id as user_id4_0_1_, comments0_.weibo_id as weibo_id5_0_1_, user1_.user_id as user_id1_2_2_, user1_.username as username2_2_2_, user1_.userpwd as userpwd3_2_2_ from comments comments0_ left outer join users user1_ on comments0_.user_id=user1_.user_id where comments0_.weibo_id=?
Hibernate: select comments0_.weibo_id as weibo_id5_0_0_, comments0_.comment_id as comment_1_0_0_, comments0_.comment_id as comment_1_0_1_, comments0_.comment_date as comment_2_0_1_, comments0_.comment_text as comment_3_0_1_, comments0_.user_id as user_id4_0_1_, comments0_.weibo_id as weibo_id5_0_1_, user1_.user_id as user_id1_2_2_, user1_.username as username2_2_2_, user1_.userpwd as userpwd3_2_2_ from comments comments0_ left outer join users user1_ on comments0_.user_id=user1_.user_id where comments0_.weibo_id=?
Hibernate: select comments0_.weibo_id as weibo_id5_0_0_, comments0_.comment_id as comment_1_0_0_, comments0_.comment_id as comment_1_0_1_, comments0_.comment_date as comment_2_0_1_, comments0_.comment_text as comment_3_0_1_, comments0_.user_id as user_id4_0_1_, comments0_.weibo_id as weibo_id5_0_1_, user1_.user_id as user_id1_2_2_, user1_.username as username2_2_2_, user1_.userpwd as userpwd3_2_2_ from comments comments0_ left outer join users user1_ on comments0_.user_id=user1_.user_id where comments0_.weibo_id=?
Hibernate: select comments0_.weibo_id as weibo_id5_0_0_, comments0_.comment_id as comment_1_0_0_, comments0_.comment_id as comment_1_0_1_, comments0_.comment_date as comment_2_0_1_, comments0_.comment_text as comment_3_0_1_, comments0_.user_id as user_id4_0_1_, comments0_.weibo_id as weibo_id5_0_1_, user1_.user_id as user_id1_2_2_, user1_.username as username2_2_2_, user1_.userpwd as userpwd3_2_2_ from comments comments0_ left outer join users user1_ on comments0_.user_id=user1_.user_id where comments0_.weibo_id=?
Hibernate: delete from comments where comment_id=?
Hibernate: delete from comments where comment_id=?
Hibernate: delete from comments where comment_id=?
Hibernate: delete from comments where comment_id=?
Hibernate: delete from comments where comment_id=?
Hibernate: delete from comments where comment_id=?
Hibernate: delete from comments where comment_id=?
Hibernate: delete from comments where comment_id=?
Hibernate: delete from comments where comment_id=?
Hibernate: delete from comments where comment_id=?
Hibernate: delete from weibo where weibo_id=?
Hibernate: delete from weibo where weibo_id=?
Hibernate: delete from weibo where weibo_id=?
Hibernate: delete from weibo where weibo_id=?
Hibernate: delete from weibo where weibo_id=?
它会一条条的发SQL去删。
在当然@Query不止是查询语言也可以用于更新和删除语句,不过需要额外添加@Modifying annotation,由于实验环境我就直接打上@Transactional声明式事务,在正式的开发环境请在你的业务层上写@Transactional,当然你也可以都写上,这样会按照你配置的或者是默认的事务传播机制进行事务传播:
@Modifying @Transactional(readOnly = false) @Query("update Weibo w set w.weiboText = :text where w.user = :user") int setUserWeiboContent(@Param("text")String weiboText,@Param("user")User user);
除了@Query annotation 还可以在Entity类中使用@NamedQuery,定义查询语句。但是笔者觉得还是不及@Query来得直接方便。
@Entity @Table(name = "users") @NamedQuery(name = "User.searchUserName",query = "select u from User u where u.username like :username") public class User {UserRepository接口定义searchUserName方法即可:
public interface UserRepository extends JpaRepository<User,Long> { List<User> searchUserName(@Param("username") String username);
四、JpaSpecificationExecutor接口
还记得刚刚暂时忽略的JpaSpecificationExecutor<Weibo>接口继承吗?在WeiboRepository接口中继承JpaSpecificationExecutor<Weibo>接口就可以在外部使用Spring提供的Criteria查询,但是这个查询也存在一些问题~ 以下就是一段范例:
@RequestMapping("/searchWeibo") public Page<Weibo> searchWeibo(final String username, final String weiboText, final Date startDate, final Date endDate,int pageNo,int pageSize) { Page<Weibo> page = this.weiboRepository.findAll(new Specification<Weibo>() { @Override public Predicate toPredicate(Root<Weibo> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) { List<Predicate> predicates = new LinkedList<>(); if (!StringUtils.isEmpty(username)) { //Join有两种方式 // Join<Weibo,User> userJoin = root.join("user",JoinType.INNER); // predicates.add(criteriaBuilder.equal(userJoin.get("username"), username)); predicates.add(criteriaBuilder.equal(root.get("user").get("username"),username)); } if (!StringUtils.isEmpty(weiboText)) { predicates.add(criteriaBuilder.like(root.get("weiboText"), "%" + weiboText + "%")); } if(startDate!=null){ predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("createDate").as(Date.class),startDate)); } if(endDate != null){ predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("createDate").as(Date.class),endDate)); } return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()])); } },new PageRequest(pageNo,pageSize)); return page; }可以直接使用findAll方法并创建匿名内部类继承Specification即可,实现toPredicate方法返回最终的条件,但是笔者想在这里使用JoinFetch,通过很多方法也未能在toPredicate实现,希望其他大牛走过路过,留下在这里使用joinFetch的方法。
五、普通的DAO
当然我们可以使用最为基础的方法去写DAO。直接@Repository 然后@PersistenceContext。最原始最基本~
@Repository public class WeiboDao { @PersistenceContext private EntityManager entityManager; @Transactional(readOnly = true) public List<Weibo> searchWeiboByEm(String username, String weiboText, Date startDate, Date endDate, int pageNo, int pageSize) { StringBuffer jpql = new StringBuffer("select w from Weibo w join fetch w.user u left join fetch w.comments c where 1=1 "); Map<String,Object> paramMap = new HashMap<>(); if(!StringUtils.isEmpty(username)){ jpql.append(" and u.username = :username"); paramMap.put("username",username); } if(!StringUtils.isEmpty(weiboText)){ jpql.append(" and w.weiboText like :weiboText"); paramMap.put("weiboText","%"+weiboText+"%"); } if(startDate!=null){ jpql.append(" and w.createDate >= :startDate"); paramMap.put("startDate",startDate); } if(endDate != null){ jpql.append(" and w.createDate <= :endDate"); paramMap.put("endDate",endDate); } Query query = entityManager.createQuery(jpql.toString()); Set<String> keys = paramMap.keySet(); for (String keyItem : keys) { query.setParameter(keyItem,paramMap.get(keyItem)); } return query.setFirstResult(pageNo*pageSize).setMaxResults(pageSize).getResultList(); } }
六、声明式事务
SpringBoot默认已经帮我们自动配置好了JPATransaction,我们直接使用即可。
直接在Service层测试@Transactional,当然正常的事务隔离级别还有事务传递方式还是和以前Spring一样配置。注意这个@Transactional是Spring提供的,不要错误的使用JPA的@Transactional
@Service public class WeiboService { @Autowired private WeiboRepository weiboRepository; @Transactional(readOnly = false,isolation = Isolation.READ_COMMITTED) public List<Weibo> importWeiboList(List<Weibo> weibos, User user){ int index = 0; Date nowDateTime = new Date(System.currentTimeMillis()); for (Weibo weiboItem: weibos) { weiboItem.setUser(user); weiboItem.setCreateDate(nowDateTime); if(5<=index++){ throw new RuntimeException("Weibo out of limit!!!"); } this.weiboRepository.save(weiboItem); } return weibos; } }
最后
以上就是安详台灯为你收集整理的SpringBoot---SpringBoot-JPA的全部内容,希望文章能够帮你解决SpringBoot---SpringBoot-JPA所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复