spring data jpa 多表 分页 多条件 查询

结论如下:

用querydsl的方式。

既然提供了这种方式就用吧。单实体,单表多条记录,动态条件直接写,不用写dao层。

多表的,大部分用querydsl都能实现。不能实现的,用sql实现。

返回值方面 还是和以前一样,返回主实体类,附加的挂在对应的类属性上。需要很高效率的用dto返回。

代码如下:

 

package com.xxx.xxxxx.service.impl;

import com.querydsl.core.BooleanBuilder;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;



@Service
public class DemoStudentServiceImpl extends BaseServiceImpl
implements DemoStudentService {

@Autowired
private DemoStudentRepository demoStudentRepository;

@Autowired
public DemoStudentServiceImpl(DemoStudentRepository demoStudentRepository) {
super(demoStudentRepository);
}

@Override
public int updateNameByStudentGuid(String name, String studentGuid){
return demoStudentRepository.updateNameByStudentGuid(name,studentGuid);
}

@Override
public long countStudentBySex(String sex) {
return demoStudentRepository.countBySex(sex);
}

@Override
public int deleteStudentByName(String name) {
//return demoStudentRepository.deleteByNameQuick(name);
return demoStudentRepository.deleteXXXByName(name);//deleteXXXByXXX ,delte 后面跟着的,by前面跟着的没所谓起啥。都支持。
}

@Override
public List< DemoStudent > removeStudentByName(String name) {
return demoStudentRepository.removeStudentByName(name);
}

/**
* Query 注解加 Modifying 注解 方式更新多条记录
* @param name
* @param birthday
* @return
*/
public int updateNameByAge(String name, String birthday) {
return demoStudentRepository.updateNameByAge(name,birthday);
}

/**
* 查询单个
* @param name
* @return
*/
@Override
public DemoStudent findByNameAndAge(String name,String age) {
return demoStudentRepository.findByNameAndAge(name,age);
}

/**
* 查询单个(动态条件)
* @param name
* @param age
* @return
*/
@Override
public DemoStudent findStudentPredicate(String name, String age) {
QDemoStudent qDemoStudent=QDemoStudent.demoStudent;
BooleanBuilder builder = new BooleanBuilder();
if(StringUtils.isNotBlank(name)){
builder.and(qDemoStudent.name.like("%"+name+"%"));
}
if(StringUtils.isNotBlank(age)){
builder.and(qDemoStudent.age.eq(age));
}
//多条记录会报错
return demoStudentRepository.findOne(builder).get();
}

/**
* 单表集合 动态查询 predicate 方式
* @param page
* @param rows
* @param name
* @param age
* @return
*/
@Override
public Page pageStudentListPredicate(Integer page, Integer rows, String name, String age) {
//查找所有的的分页
//demoStudentRepository.findAll(PageRequest.of(page-1,rows));

//分页加排序加参数查询
QDemoStudent qDemoStudent=QDemoStudent.demoStudent;
BooleanBuilder builder = new BooleanBuilder();
if(StringUtils.isNotBlank(name)){
builder.and(qDemoStudent.name.like("%"+name+"%"));
}
if(StringUtils.isNotBlank(age)){
builder.or(qDemoStudent.age.eq(age));
}

return demoStudentRepository.findAll(builder,PageRequest.of(page-1,rows,Sort.by("age").descending()));
}

/**
* 多表集合 动态查询 predicate 方式,返回方式为主类,副类或者副类的其他字段以其他方式set进主类
* @param page
* @param rows
* @param name
* @param age
* @return
*/
@Override
public Page pageStudentClassListPredicate(Integer page, Integer rows, String name, String age) {
//分页加排序加参数查询
QDemoStudent qDemoStudent=QDemoStudent.demoStudent;
BooleanBuilder builder = new BooleanBuilder();
if(StringUtils.isNotBlank(name)){
builder.and(qDemoStudent.name.like("%"+name+"%"));
}
if(StringUtils.isNotBlank(age)){
builder.or(qDemoStudent.age.eq(age));
}
return demoStudentRepository.pageStudentClassListPredicate(builder,PageRequest.of(page-1,rows));
}

/**
* 多表查询,采用dsl的方式,返回自定义对象
* @return
*/
@Override
public List < DemoStudentVO > findStudentClassDslDTO(){
return demoStudentRepository.findStudentClassDslDTO();
}


/**
* 多表查询,原始查询的方式(native sql),并且按指定的resultSetMapping返回
* @return
*/
@Override
public Page pageStudentClassSqlMapResult(Integer page, Integer rows, String name, String age){
return demoStudentRepository.pageStudentClassSqlMapResult(PageRequest.of(page-1,rows),name,age);
}

/**
* 多表集合 多条件,query注解, new DemoStudentVO 对象返回, 需要实体manytoone支持表关联
* @param page
* @param rows
* @param name
* @param age
* @return
*/
@Override
public Page pageStudentClassListJPQLDTO(Integer page, Integer rows, String name, String age) {
return null;
//return demoStudentRepository.pageStudentClassListJPQLDTO(name,age,PageRequest.of(page-1,rows,Sort.by("age").descending()));
}


/**
* 多表集合 多条件,query注解,native sql 方式,默认返回object[]
* @param page
* @param rows
* @param name
* @param age
* @return
*/
@Override
public Page < Object[] > pageStudentClassListSql(Integer page, Integer rows, String name, String age) {
return demoStudentRepository.pageStudentClassListSql(name,age,PageRequest.of(page-1,rows,Sort.by("age").descending()));
}



/**
* 使用Specification 方式分页
* @param page
* @param rows
* @param name
* @param age
* @return
*/
public Page < DemoStudent > pageStudentClassSpecification(Integer page, Integer rows, String name, String age) {
//规格定义
Specification specification = new Specification() {
/**
* 构造断言
* @param root 实体对象引用
* @param query 规则查询对象
* @param cb 规则构建对象
* @return 断言
*/
@Override
public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
List predicates = new ArrayList<>(); //所有的断言
if(StringUtils.isNotBlank(name)){ //添加断言
Predicate likeName = cb.like(root.get("name").as(String.class),name+"%");
predicates.add(likeName);
}
return cb.and(predicates.toArray(new Predicate[0]));
}
};
//分页信息
Pageable pageable = PageRequest.of(page-1,rows); //页码:前端从1开始,jpa从0开始,做个转换
//查询
return demoStudentRepository.findAll(specification,pageable);
}

}

 

package com.xxxx.xxxxxx.repository.custom.impl;

import com.querydsl.core.QueryResults;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.Projections;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import freemarker.template.utility.DateUtil;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.SQLQuery;
import org.hibernate.query.NativeQuery;
import org.hibernate.transform.Transformers;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.Querydsl;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DemoStudentRepositoryImpl implements DemoStudentRepositoryCustom {

    @PersistenceContext
    private EntityManager entityManager;

    //查询工厂实体
    private JPAQueryFactory queryFactory;

    @PostConstruct
    public void init(){
        queryFactory = new JPAQueryFactory(entityManager);
    }

    /**
     * 多表查询,采用dsl的方式,返回单个实体的page列表,单实体中有其他实体
     * @return
     */
    @Override
    public Page pageStudentClassListPredicate(Predicate predicate, Pageable pageable){
        QDemoStudent qDemoStudent = QDemoStudent.demoStudent;
        QDemoClass qDemoClass = QDemoClass.demoClass;
        JPAQuery jpaQuery = queryFactory.select(qDemoStudent,qDemoClass)
                .from(qDemoStudent).leftJoin(qDemoClass).on(qDemoStudent.classGuid.eq(qDemoClass.classGuid))
                .where(predicate)
                .orderBy(qDemoStudent.age.desc())
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize());

        QueryResults result=jpaQuery.fetchResults();
        List lsStudent=new ArrayList ();
        for (Tuple row : result.getResults()) {
            DemoStudent student= row.get(qDemoStudent);
            student.setDemoClass(row.get(qDemoClass));
            lsStudent.add(student);
        }
        Page pageDemoStudent=new PageImpl < DemoStudent >(lsStudent,pageable,result.getTotal());
        return pageDemoStudent;
    }


    /**
     * 多表查询,采用dsl的方式,返回自定义对象
     * @return
     */
    @Override
    public List < DemoStudentVO > findStudentClassDslDTO(){
        QDemoStudent qDemoStudent = QDemoStudent.demoStudent;
        QDemoClass qDemoClass = QDemoClass.demoClass;

        //用下面的办法或者直接返回Tuple 对象,一个个set,例如:dto.setPrice(tuple.get(_Q_good.price));
        return queryFactory
                .select(
                        Projections.bean(
                                DemoStudentVO.class,//返回自定义实体的类型 这种方式,dto对象不要用构造函数,会报错
                                qDemoStudent.studentGuid,
                                qDemoStudent.name,
                                qDemoStudent.age,
                                qDemoStudent.birthday,
                                qDemoClass.className,
                                qDemoClass.remark.as("classRemark")//使用别名对应dto内的typeId
                        )
                )
                .from(qDemoStudent,qDemoClass)//构建两表笛卡尔集
                .where(qDemoStudent.classGuid.eq(qDemoClass.classGuid))//关联两表
                .orderBy(qDemoStudent.age.desc())//倒序
                .fetch();

    }


    /**
     * 多表查询,原始查询的方式(native sql),并且按以前hibernate方式返回
     * @return
     */
    @Override
    public Page pageStudentClassSqlMapResult(Pageable pageable, String name, String age) {
        StringBuilder countSelectSql = new StringBuilder();
        countSelectSql.append("select count(1)  from demo_student s "+
                " left join  demo_class c on s.class_guid=c.class_guid where 1=1 ");

        StringBuilder selectSql = new StringBuilder();

        selectSql.append("select {s.*},{c.*}  from demo_student s "+
                " left join  demo_class c on s.class_guid=c.class_guid where 1=1 ");

        Map params = new HashMap<>();
        StringBuilder whereSql = new StringBuilder();
        if(StringUtils.isNotBlank(name)){
            whereSql.append(" and name=:name ");
            params.put("name",name);
        }
        if(StringUtils.isNotBlank(age)){
            whereSql.append(" and age=:age ");
            params.put("age",age);
        }

        String countSql = new StringBuilder().append(countSelectSql).append(whereSql).toString();
        Query countQuery = entityManager.createNativeQuery(countSql);


        for(Map.Entry entry:params.entrySet()){
            countQuery.setParameter(entry.getKey(),entry.getValue());
        }
        BigInteger totalCount = (BigInteger)countQuery.getSingleResult();

        String querySql = new StringBuilder().append(selectSql).append(whereSql).toString();

        // select s.*,c.* 这种,两个表有相同字段的,因为第二个表的对应字段会用第一个表的对应字段,数据信息不对。
        //Query query = this.entityManager.createNativeQuery(querySql,"StudentResults");
        Query query = this.entityManager.createNativeQuery(querySql);
        for(Map.Entry entry:params.entrySet()){
            query.setParameter(entry.getKey(),entry.getValue());
        }
        query.setFirstResult((int)pageable.getOffset());
        query.setMaxResults(pageable.getPageSize());

        //query.unwrap(SQLQuery.class).addEntity("s",DemoStudent.class).addEntity("c",DemoClass.class);
        query.unwrap(NativeQuery.class).addEntity("s",DemoStudent.class).addEntity("c",DemoClass.class);
        List result =query.getResultList();//是object[]数组,第一个元素是demo_student对象,第二个元素是demo_class对象
        List lsStudent=new ArrayList ();
        for (Object row : result) {
            DemoStudent demoStudent=(DemoStudent)((Object[])row)[0];
            DemoClass demoClass=(DemoClass)((Object[])row)[1];
            demoStudent.setDemoClass(demoClass);
            lsStudent.add(demoStudent);
        }
        Page pageDemoStudent=new PageImpl < DemoStudent >(lsStudent,pageable,totalCount.longValue());
        return pageDemoStudent;
    }


}

 

 

参考:http://www.ityouknow.com/spring-boot.html

https://www.cnblogs.com/ityouknow/p/5891443.html

几种方式

1.hibernate方式 字段上面加manytoone什么之类的。不喜欢这种,有时候对象之间关联很多,不知道什么时候调用了。虽然有懒加载,但是有时候关联的另外的表很大,关联的表很多,就不可控,多表还是sql语句吧。

2.jpql方式1 通过new 对象的方式传入参数,作为返回对象,自定义对象

参考:https://blog.csdn.net/phapha1996/article/details/78994395

  1. @Query(value = "select new org.fage.vo.UserOutputVO(u.name, u.email, d.name as departmentName, count(r.id) as roleNum) from User u "

  2. + "left join u.department d left join u.roles r group by u.id")

  3. Page findUserOutputVOAllPage(Pageable pageable);

这里注意一下,VO中的构造方法参数一定要与查询语句中的查询字段类型相匹配(包括数量),如果不匹配就会报错。

不过如果对象很多参数,岂不是要列很多,而且每个地方不一样,得多个构造函数?不能弄实体对象里面带实体这种么,非得是都是第一层的?构造函数应该也可以,传进去,构造函数里面处理,设置对象

不过话说回来分页的话,也不需要那么多字段

不过对应的实体类上还是需要manytoone,这种来关联表

3.jpql方式2 使用projection接口做映射与投影

  1. public interface UserProjection {

  2. String getName();

  3.  
  4. @Value("#{target.emailColumn}")//当别名与该getXXX名称不一致时,可以使用该注解调整

  5. String getEmail();

  6.  
  7. String getDepartmentName();

  8.  
  9. Integer getRoleNum();

  10. }

  11. //故意将email别名为emailColumn,以便讲解@Value的用法

  12. @Query(value = "select u.name as name, u.email as emailColumn, d.name as departmentName, count(r.id) as roleNum from User u "

  13. + "left join u.department d left join u.roles r group by u.id")

  14. Page findUserProjectionAllPage(Pageable pageable)

这种方式的对比上面的好处是不用在代码里写new,但是需要通过返回接口的方式,感觉怪怪的?

 

https://blog.csdn.net/qq_36144258/article/details/80298354

map方式的返回:

/** * HQL通过旅店名称查询旅店以及城市的所有信息 * * @return */ @Query(value = "select new map(t1,t2) from TCity t1 left join THotel t2 on t1.id=t2.city where t2.name =:name") List> findCityAndHotelByHQL(@Param("name") String name);

 

 

4.原生sql方式

  1. //展示原生查询

  2. @Query(value = "select u.name as name, u.email as emailColumn, d.name as departmentName, count(ur.role_id) as roleNum from user u "

  3. + "left join department d on d.id=u.department_id left join user_role ur on u.id=ur.user_id group by u.id limit :start,:size",

  4. nativeQuery = true)

  5. List findUserProjectionAllPageNative(@Param("start")int start, @Param("size")int size);

默认返回是list

 

Pageable pageable = new PageRequest(0,5);

  1. //如果需要的话,自行封装分页信息

  2. Page page = new PageImpl(content, pageable, total);

当你需要进行sql优化时,可能用原生sql方式会更好。但是一般需求时候用JPQL还是比较方便的,毕竟这样比较省事,拿数据总是需要分页的,有时候只需要拿几个字段也是这样。

参考:https://blog.csdn.net/qq_36144258/article/details/80298354

/** * 通过旅店名称分页查询旅店以及城市的信息 * * @param name 旅店名称 * @param pageable 分页信息 * @return Page */ @Query(value = "select t1.name as cityName,t2.name as hotelName\n" + "from t_city t1\n" + " left join t_hotel t2 on t2.city = t1.id\n" + "where t2.name = :name", countQuery = "select count(*)" + "from t_city t1 \n" + " left join t_hotel t2 on t2.city = t1.id\n" + "where t2.name = :name" , nativeQuery = true) Page findCityAndHotel(@Param("name") String name, Pageable pageable);

 

5.集映射定义

map result

https://www.v2ex.com/t/333899

 

6.改为原生方式,直接写,entitimanager

参考:https://blog.csdn.net/qq_36144258/article/details/80298354

@Autowired @PersistenceContext private EntityManager entityManager; @Test public void testDynamic() throws Exception { String sql = "select new pers.zpw.domain.CityHohel(t1.name AS cityName,t2.name AS hotelName) from TCity t1 left join THotel t2 on t1.id=t2.city where t2.name ='酒店'"; Query query = entityManager.createQuery(sql); List resultList = query.getResultList(); Assert.assertTrue(resultList.size() > 0); }

 

7.还有一种,自己写方法,讲方位的object对象转成对应对象

https://blog.csdn.net/pp_fzp/article/details/80530588

 

8.映射成实体,这种返回是不错,不过要加在entity上面一大坨,感觉不好,别人2010年就写了,佩服

http://www.blogjava.net/jesson2005/articles/380884.html

补充:返回的object数组是 比如 object[0]学生实体 加 object[1] 班级的名称(下面代码如下),比如是object[0]学生实体 加 object[1]班级实体(修改例子中,entities 多个就行)

https://docs.oracle.com/javaee/5/api/javax/persistence/SqlResultSetMapping.html

参考:https://blog.jooq.org/tag/sqlresultsetmapping/

这种方式,上个链接说了不好,另外从上面这个链接读到另外一篇文章,那个家伙一样,对jpa这种方式也是很不喜欢,觉得还是sql的好,https://blog.jooq.org/2013/11/13/popular-orms-dont-do-sql/

这两种都可以实现。

总结:

1.SqlResultSetMapping  里对应的所有列要写,查询那边也需要写上所有的列,因为本来他就是找表字段对应实体关系,如果有一边缺少,就会报错。两边要完全都列上所有的。(后面发现,如果不存在列名为别名的情况,不用,直接不用写fileds,而且也不存在别名的情况,要处理。不过如果写了,就都要写全。不写就好了)

2.另外大量重复用到的话,如果某些关联是要用别名的话,将不好修改。参考文中这段话(如果是实体加某1,2个列可能存在,不过名字应该可以起的稍微不一样,如果是两个实体,重复的名字不会报错,所以应该问题也不大)

Did you notice the difference? The b.title column was renamed to book_title. In a SQL string. Which blows up at run time! How to remember that you have to also adapt

1

@FieldResult(name = "title", column = "title")

… to be

1

@FieldResult(name = "title", column = "book_title")

Conversely, how to remember that once you rename the column in your @FieldResult, you’ll also have to go check wherever this "BookAuthorMapping" is used, and also change the column names in those queries.

 

3.另外很大一坨,而且可能要加很多坨,因为不同的地方返回的不一样,特别是大量多表链接的话。(fields可以省略,如果不需要额外其别名的话,一般也不用)

代码如下:


@SqlResultSetMapping (
        name="StudentResults",
        entities = {
                @EntityResult(entityClass = DemoStudent.class,fields={
                        @FieldResult(name="studentGuid",column = "student_guid"),
                        @FieldResult(name="name",column = "name"),
                        @FieldResult(name="age",column = "age"),
                  @FieldResult(name="sex",column = "sex"),
                  @FieldResult(name="birthday",column = "birthday"),
                  @FieldResult(name="classGuid",column = "class_guid"),
                  @FieldResult(name="isDeleted",column = "is_deleted")
                  /*@FieldResult(name="className",column = "class_name") 直接想写在这里,直接把班级表的名称映射到这个实体类的transient字段中,是不行的,必须是数据库的,不报错,但是也不会填充值*/
                })
        },
        columns = {
                @ColumnResult(name="class_name")
        }
)
@Entity
@DynamicInsert (true)
@DynamicUpdate (true)
@Table (name = "Demo_Student")
@Where (clause="Is_Deleted=0")
public class DemoStudent implements java.io.Serializable{

   private static final long serialVersionUID = -1L;

   private String studentGuid;

   private String name;

   private String age;

   private String sex;

   private Date birthday;

/* private DemoClass demoClass;*/


   private String classGuid;

   private String className;

   private Boolean isDeleted;


   public DemoStudent(){
   }

   public DemoStudent(
      String studentGuid
   ){
      this.studentGuid = studentGuid;
   }

   @Id
   @GeneratedValue (generator = "pk")
   @GenericGenerator (name = "pk", strategy = "uuid2")
   @Column (name = "student_guid")
   public String getStudentGuid() {
      return this.studentGuid;
   }
   
   public void setStudentGuid(String value) {
      this.studentGuid = value;
   }

   @Column (name = "name")
   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }

   @Column (name = "age")
   public String getAge() {
      return age;
   }

   public void setAge(String age) {
      this.age = age;
   }

   @Column (name = "sex")
   public String getSex() {
      return sex;
   }

   public void setSex(String sex) {
      this.sex = sex;
   }

   @Column (name = "birthday")
   public Date getBirthday() {
      return birthday;
   }

   public void setBirthday(Date birthday) {
      this.birthday = birthday;
   }

/* @ManyToOne
   @JoinColumn(name="class_guid",insertable = false,updatable = false)*/
/* @Transient
   public DemoClass getDemoClass() {
      return demoClass;
   }

   public void setDemoClass(DemoClass demoClass) {
      this.demoClass = demoClass;
   }*/

   @Column (name = "class_guid")
   public String getClassGuid() {
      return classGuid;
   }

   public void setClassGuid(String classGuid) {
      this.classGuid = classGuid;
   }

   @Column (name = "is_deleted")
   public Boolean getIsDeleted() {
      return isDeleted;
   }

   public void setIsDeleted(Boolean isDeleted) {
      this.isDeleted = isDeleted;
   }

   @Transient
   public String getClassName() {
      return className;
   }

   public void setClassName(String className) {
      this.className = className;
   }
}


@Repository
public class DemoStudentDaoImpl implements DemoStudentDao{
    //实体管理
    @Autowired
    private EntityManager entityManager;

    //查询工厂
    private JPAQueryFactory queryFactory;

    public DemoStudentDaoImpl(){
        queryFactory=new  JPAQueryFactory(entityManager);
    }

    @Override
    public void pageStudentClassListDsl(PageRequest page) {
        Query q = entityManager.createNativeQuery(
                "select s.student_guid,s.name,s.age,s.birthday,s.is_deleted,s.sex,s.class_guid,c.class_name  from demo_student s " +
                        "left join  demo_class c on s.class_guid=c.class_guid ",
                "StudentResults");

        List resultList =q.getResultList();
  //这里的结果就是object数组的集合,每个object数组两个元素,第一个元素是学生实体,第二个就是classname对应的字符串


      return null;
    }
}

 

9.ConstructorResult 方式

类似

@SqlResultSetMapping( name = "view", classes = { @ConstructorResult( targetClass = TA.class, columns={ @ColumnResult(name = "name",type = String.class), @ColumnResult(name = "time",type = Date.class), @ColumnResult(name = "bname",type = String.class) } ) } )

这种,换要对应构造函数不方便。参考:https://www.v2ex.com/t/333899

 

6.弄成视图,好了,变成单表操作了

7.QueryDSL 方式,用这种把
https://www.jianshu.com/p/5c416a780b3e 

 

8.返回对象不好处理,改为以前的sqlquery,好处理了。。啥都能处理了,虽然有点low,不过比mapresult那种方式简单。就这样吧。

query.unwrap(SQLQuery.class).addEntity("s",DemoStudent.class).addEntity("c",DemoClass.class);

 参考:https://blog.csdn.net/zhu562002124/article/details/75097682

你可能感兴趣的:(spring,boot,spring,data,jpa)