spring data jpa 多表 分页 多条件 查询
结论如下:
用querydsl的方式。
鉴于此提供了一种方式,则建议采用该方法进行处理。对于单一实体对象而言,在单一表中可容纳多条记录的情况,则只需将动态条件直接设置为字段值即可,并无需在数据库层面进行关联关系的处理
多表的,大部分用querydsl都能实现。不能实现的,用sql实现。
在返回值方面,并无变化,在主实体类的基础上附加项被绑定到相应的属性中。要求DTO格式必须高效且规范
代码如下:
`package com.xxx.xxxxx.service.impl;
以下是基于给定规则对输入文本进行的同义改写
import javax.persistence_criteria_criterion_criteria_builder;
import javax_persistence_criteria_criterion_query;
import javax_persistence_criteria_criterion_predicate;
import javax_persistence_criteria_criterion_root;
import java.util.allocationlist;
import java.util collections list;
无法对这段代码进行有效的同义改写以降低重复率
@Autowired
private DemoStudentRepository demoStudentRepository;
@Autowired public DemoStudentServiceImpl(DemoStudentRepository demoStudentRepository) { super(demoStudentRepository); }
@Override
public int updateNameByStudentID(String name, String studentID) {
return demoStudentRepository.updateNameByStudentID(name, studentID);
}
@Override
public long countStudentsBySex(String gender) {
计算结果返回 demoStudentRepository.countBySex(gender);
}
@Override
public int deleteStudentByName(String name) {
// 调用演示学生存储库快速删除特定名称的学生
return demoStudentRepository.deleteXXXByName(name); // 删除操作支持通过名称快速执行
}
@Override
public List< DemoStudent > removeStudentByName(String name) {
returns demoStudentRepository.removeStudentByName(name);
}
/**
-
通过查询注释与修改注释的方式更新多条记录信息
-
输入参数:
-
@param name - 姓名
-
@param birthday - 出生日期
-
输出结果:
-
@return - 更新后的学生成绩信息编号
*/
public int updateNameByAge(String name, String birthday) {
// 通过查询注释与修改注释的方式获取学生数据
Student student = queryDemoStudentRepository();
// 调用实现该功能的具体方法
return student.updateNameByAge(name, birthday);
} -
单个查询
-
@参数名
-
@返回值
/**
- 查询单个(动态条件)
- @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();
}
/**
- 单一表关联 按需动态查询 谓词方式
- @param page
- *page: 当前分页号
- @param rows
- *rows: 每页行数
- @param name
- *name: 学生姓名
- @param age
- *age: 学生年龄
*/
@SuppressWarnings("unchecked")
public PagepageStudentListPredicate(
Integer page,
Integer rows,
String name,
String age) {
// 根据指定分页号及行数获取对应结果集
// 通过谓词查询获取对应的学生信息
return demoStudentRepository.findAll(
new PageRequest(page - 1, rows));
}
根据分页、排序及参数查询需求,
将结果集分配给变量qDemoStudent。
初始化布尔过滤器builder。
如果字符串name不为空,则执行以下操作:
将过滤条件追加到builder中。
如果字符串age不为空,则使用OR逻辑将年龄等于age的条件追加到builder中。
该方法用于从demoStudentRepository获取所有学生数据,并通过builder构建查询条件进行筛选。
筛选条件包括指定页数前一页的数据总量、总记录数以及按年龄降序排列的结果。
/**
- 多表集合 动态查询 predicate 方式,返回方式为主类,副类或者副类的其他字段以其他方式set进主类
- @param page
- @param rows
- @param name
- @param age
- @return
*/
@Override
public PagepageStudentClassListPredicate(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 PagepageStudentClassSqlMapResult(Integer page, Integer rows, String name, String age){
return demoStudentRepository.pageStudentClassSqlMapResult(PageRequest.of(page-1,rows),name,age);
} -
涉及多个表的集合操作,并包含多个条件,
-
使用查询注解创建并返回DemoStudentVO对象,
-
必须支持many-to-one关系以实现表间关联
-
@param page
-
@param rows
-
@param name
-
@param age
-
@return 学生信息列表
-
多表集合查询需满足多条件要求,默认启用 native SQL 方式,默认返回一维数组
-
@param page
-
@param rows
-
@param name
-
@param age
-
@return 一维数组
/**
- 使用Specification 方式分页
- @param page
- @param rows
- @param name
- @param age
- @return
/
public Page < DemoStudent > pageStudentClassSpecification(Integer page, Integer rows, String name, String age) {
//规格定义
Specificationspecification = new Specification *() {
/ - 构造断言
- @param root 实体对象引用
- @param query 规则查询对象
- @param cb 规则构建对象
- @return 断言
*/
@Override
public Predicate toPredicate(Rootroot, CriteriaQuery<?> query, CriteriaBuilder cb) {
Listpredicates = 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<DemoStudent> pageStudentClassListPredicate(Predicate predicate, Pageable pageable){
QDemoStudent qDemoStudent = QDemoStudent.demoStudent;
QDemoClass qDemoClass = QDemoClass.demoClass;
JPAQuery<Tuple> 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<Tuple> result=jpaQuery.fetchResults();
List<DemoStudent> lsStudent=new ArrayList <DemoStudent>();
for (Tuple row : result.getResults()) {
DemoStudent student= row.get(qDemoStudent);
student.setDemoClass(row.get(qDemoClass));
lsStudent.add(student);
}
Page<DemoStudent> 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<DemoStudent> 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<String,Object> 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<String,Object> 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<String,Object> 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<DemoStudent> lsStudent=new ArrayList <DemoStudent>();
for (Object row : result) {
DemoStudent demoStudent=(DemoStudent)((Object[])row)[0];
DemoClass demoClass=(DemoClass)((Object[])row)[1];
demoStudent.setDemoClass(demoClass);
lsStudent.add(demoStudent);
}
Page<DemoStudent> 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
几种方式
在Hibernate配置中使用字段间采用manytoone方式进行关联的方式存在不满,在实际应用中遇到多个对象之间的关联情况时容易出现超时问题。即便采用延迟加载机制,在涉及的数据表规模较大时也难以掌控其运行效果。对于这种情况是否应该改用多表策略或直接转为sql语句处理仍存在疑问。
2.jpql方式1 通过new 对象的方式传入参数,作为返回对象,自定义对象
参考:<>
@Query(value: 从新组织架构中选择组织功能模块中的UserOutputVO对象,
包含用户姓名、邮箱,
部门名称字段并命名为departmentName,
以及参与者的数量字段并命名为roleNum
from User u )
+ "left join u.department d left join u.roles r group by u.id")
Page
这里请注意:VO的构造方法参数必须与查询语句中的查询字段类型及数量一致。如果无法匹配则会抛出错误。
假如涉及的对象参数较多,
难道不需一一列出?
而且每个部分都有各自的特点,
这样就需要为每个情况定制不同的构造函数。
非得是都是第一层的?
或者可以直接通过传递参数的方式?
构造函数应该也可以通过传递参数的方式进行处理,
在内部进行配置设置。
不过话说回来分页的话,也不需要那么多字段
不过对应的实体类上还是需要manytoone,这种来关联表
3.jpql方式2 使用projection接口做映射与投影
public interface UserProjection {
String getName();
@value(target.email_column)//当别名与对应的getXXX方法名称不一致时,可以通过以下方式实现参数注入
String getEmail();
String getDepartmentName();
Integer getRoleNum();
}
//故意将email别名为emailColumn,以便讲解@Value的用法
@quer从User表中选择用户名、邮箱、部门名称以及角色数量。
该查询用于从用户表获取基本信息及职位分布情况。
其中包括每位用户的基本信息如用户名、邮箱地址等,
以及对应部门名称和具体参与的角色数量。
这种数据结构有助于后续分析与管理。
通过这种聚合查询,
可以高效地提取出所需的关键信息。
这种数据处理方法在实际应用中具有较高的实用价值。
通过合理设计查询语句,
可以进一步优化数据提取效率,
并支持复杂的业务逻辑实现。
+ "left join u.department d left join u.roles r group by u.id")
Page
对比之下这种方式的效果如何?它无需在代码中显式声明new变量。但必须借助返回接口的方式来处理相关信息。整体上来说总觉着有些别扭。
<>
map方式的返回:
/** * HQL基于旅店名称检索酒店及其所在城市的所有信息 * * @return */ @Query(value = "从带左连接的城市表t1和酒店表t2中提取记录" select new map(t1,t2) from TCity t1 left join THotel t2 on t1.id=t2.city where t2.name=:name) List<Map<String, Object>> findCityAndHotelByHQL(@Param("name") String name);
4.原生sql方式
//展示原生查询
@Command(value="从用户表中选择员工名称、电子邮件字段、部门名称以及角色数量统计")
部门信息左连接到u上(根据id匹配),接着角色表ur也左连接到u上(根据id匹配),然后按u的id进行分组,并限制开始索引和大小
nativeQuery = true)
List<Object[]> 用户投影的全部页面 @Param("起始编号") int 起始编号, @Param("数量限制") int 数量限制
默认返回是list<Object[]>
Pageable pageable = new PageRequest(0,5);
//如果需要的话,自行封装分页信息
Page<Object[]> page = new PageImpl<Object[]>(content, pageable, total);
当在处理数据库性能优化问题时,采用原生SQL的方式可能会更高效。然而,在大多数需求场景下使用JSON Path Query语言(JPQL)仍然较为便捷。这无疑是一种更为简便的方法。需要注意的是,在获取数据时通常都需要 pagination(分页)操作来处理大量的数据条目。当然,在某些情况下仅需获取几个字段也同样适用。
参考:<>
/** * 基于旅客姓名进行分页检索的旅馆及所在城市信息 * * @param name 旅客姓名 * @param pageable 分页参数 * @return Page<Object[]> */
@Query(
value = "SELECT CityName AS city_name, HotelName AS hotel_name",
value = value,
countQuery = "SELECT COUNT(*) FROM city_info city JOIN hotel_info hotel ON hotel.city_id = city.city_id WHERE hotel.hotel_name = :name",
nativeQuery = true
)
public function findCityAndHotel(
?string name,
Pageable $pageable
): Page<Object[]>
{
}
5.集映射定义
map result
6.改为原生方式,直接写,entitimanager
参考:<>
@Autowired @PersistenceContext private EntityManager entityManager;
@Test public void testDynamic() throws Exception {
根据配置生成对应的CityHohel表单;
String sql = "选择新业务流程中心生成的城市表单;
从TCity表t1左连接THotel表t2,
其中t1的id字段等于t2的city字段;
并且筛选出t2.name='酒店'的数据";
根据上述SQL语句,
Query query = entityManager.createQuery(sql);
通过查询获取结果集列表;
声明并赋值resultList变量;
验证结果集非空:
resultList.size() > 0";
}
7.还有一种,自己写方法,讲方位的object对象转成对应对象
<>
将数据映射到实体对象中是一种有效的做法。然而,在实际应用中发现该方法存在一定的问题:若仅将该返回添加到实体对象上,则可能会导致数据冗余。值得注意的是,在2010年时已有研究者已经实现了类似的功能
http://www.blogjava.net/jesson2005/articles/380884.html
补充:返回的对象数组包含例如在对象数组中, object[0]表示 学生实体 ,而 object[1]则代表 班级实体 (例如以下代码段),其中在修改后的示例中 entities 数量可扩展至多个。
该文档提供了关于JavaEE持久化框架中ResultSetMapping的具体实现细节和技术规范说明,请参考完整文档获取详细信息。
参考:https://blog.jooq.org/tag/sqlresultsetmapping/
这种技术,在另一个网站上也有批评,并且从另一篇博客中了解到相关观点;同样地,在另一篇文章中提到该作者对该技术持反对态度,并认为jpa在这方面不如传统的sql方法好;值得参考的地方是https://blog.jooq.org/2013/11/13/popular-orms-dont-do-sql/。
这两种都可以实现。
总结:
在SqlResultSetMapping中对应的所有字段需填写,在查询那一端也需要完整列出所有相关字段。
因为其本质是将数据库表中的字段与实体对象的属性进行一一对应。
若缺少任一端的字段对应关系,则会导致错误提示。
两端必须确保所有对应的字段都被完整列出。
如果大量使用相同的名字可能会导致修改困难。例如文中提到的那段话:如果某些关联需要使用别名,则会遇到问题。例如如果是实体加上最多一两个列,则可能需要重新命名以避免冲突;但名称最好能稍微调整以避免冲突的情况通常不成问题。
发现了差异吗?b.title字段重命名为book_title在SQL字符串中。The performance issue crashes at runtime.How can one keep in mind that they must also adapt
| 1 | @FieldResult``(name = ``"title"``, column = ``"title"``) |
|---|
… to be
| 1 | @FieldResult``(name = ``"title"``, column = ``"book_title"``) |
|---|
Conversely, how to recall that after renaming the column in your @FieldResult, you must also verify wherever this "BookAuthorMapping" is referenced, and update the column names in those queries.
除此之外还有非常庞大的数量,并且可能需要添加大量的分表来处理这些数据(例如当各个表返回的数据结构存在差异时)。尤其是当存在大量分表链接时,默认情况下系统会自动解析并获取所有相关的字段信息)。需要注意的是字段名称不需要显式声明即可默认获取数据(例如如果不做额外设置系统会自动推断并获取所有相关字段)。通常情况下也可以忽略不计这些默认获取的字段名(因为它们并不会对查询结果产生实际影响)。
代码如下:
@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 方式
类似
该SQL ResultSet Mapping配置将设置为名称 'view' 以及其包含的类。该映射作用于类 TA,并包括以下字段配置:字段名为 'name' 且类型设为字符串;字段名为 'time' 且类型设为日期;字段名为 'bname' 且类型设为字符串。
这种,换要对应构造函数不方便。参考:https://www.v2ex.com/t/333899
6.弄成视图,好了,变成单表操作了
7.QueryDSL 方式,用这种把
https://www.jianshu.com/p/5c416a780b3e
- 遇到返回对象时难以管理, 因此我们决定替换成之前的SQL查询方式, 这样变得容易操作. 该方法能够应对各种情况, 虽然显得略显不足, 但相比起来更为简便.
query.unwrap(SQLQuery.class).addEntity("s",DemoStudent.class).addEntity("c",DemoClass.class);
参考:<>
