jpa多对多,中间表有额外查询字段设计,以及使用creteriaquery进行动态查询,使用自定义类进行数据封装返回

一.jpa多对多__利用一对多实现(推荐)

尽量不要使用@manyToMany,特别是中间表有冗余字段的时候;
最好是在两个主表中加上@oneToMany,从表中加上@manyToOne来配置,加强jpa对中间表的支持度
!!!注意所有主表中的对应关系都是和映射表建立的

①表结构

学生表(student)

字段名字段中文类型描述
pk_stu_id学生idlong主键
stu_name学生姓名varchar(10)
uk_stu_num学生学号具有唯一性

学科表(discipline)

字段名字段中文类型描述
pk_dis_id学科id主键
dis_name学科名称
uk_dis_num学科编号具有唯一性

学生学科映射表(student_discipline_mapping)

字段名字段中文类型描述
pk_stu_id_mp学生id主键
pk_dis_id_mp学科id
grade成绩
years成绩所对应年份
semester成绩所对应年份的学期0代表上学期;1代表下学期

②po类对应

/**
 * @author heChangSheng
 * @date 2020/12/9 : 15:16
 */
@ApiModel("学生表实体类")
@Entity
@Table(name = "student")
@EqualsAndHashCode(exclude = {"teacherPos", "disciplinePos"})
@Getter
@Setter
@NoArgsConstructor
public class StudentPo {

    @ApiModelProperty("学生id,主键")
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "pk_stu_id")
    private Long stuId;

    @ApiModelProperty("学生姓名")
    @Column(name = "stu_name")
    private String stuName;

    @ApiModelProperty("学生学号,唯一索引")
    @Column(name = "stu_num")
    private String stuNum;

    /**和学生学科映射类中的学生做映射关系**/
    @ApiModelProperty("保存课程集合,课程_学生多对多设置")
    @OneToMany(mappedBy = "studentPo", fetch = FetchType.LAZY, orphanRemoval = true,cascade = CascadeType.PERSIST)
    private Set<StudentDisciplineMappingPo> disciplinePos = new HashSet<>();


    @Override
    public String toString() {
        return "StudentPo{" +
                "stuId=" + stuId +
                ", stuName='" + stuName + '\'' +
                ", stuNum='" + stuNum + '\'' +
                ", teacherPos=" + teacherPos +
                ", disciplinePos=" + disciplinePos +
                '}';
    }

    public StudentPo(String stuName) {
        this.stuName = stuName;
    }
}

/**
 * @author heChangSheng
 * @date 2020/12/9 : 23:36
 */
@ApiModel("课程表实体类")
@Entity
@Table(name = "discipline")
@Getter
@Setter
@NoArgsConstructor
public class DisciplinePo extends PagingEntity {

    @ApiModelProperty("课程id,主键")
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "pk_dis_id")
    private Long disId;

    @ApiModelProperty("课程名字")
    @Column(name = "dis_name")
    private String disName;

    @ApiModelProperty("课程编号,唯一索引")
    @Column(name = "uk_dis_num")
    private String disNum;

    @ApiModelProperty("保存学生集合,课程_学生多对多设置")
    @OneToMany(mappedBy = "disciplinePo", orphanRemoval = true, fetch = FetchType.LAZY,cascade = CascadeType.PERSIST)
    private Set<StudentDisciplineMappingPo> studentPos = new HashSet<>();

    @ApiModelProperty("保存老师集合,课程_老师多设置")
    @OneToMany(mappedBy = "disciplinePo", orphanRemoval = true, fetch = FetchType.LAZY,cascade = CascadeType.PERSIST)
    private Set<TeacherDisciplineMappingPo> teacherPos = new HashSet<>();

    @Override
    public String toString() {
        return "DisciplinePo{" +
                "disId=" + disId +
                ", disName='" + disName + '\'' +
                ", disNum='" + disNum + '\'' +
                ", page=" + page +
                ", size=" + size +
                '}';
    }
}
/**
 * @author heChangSheng
 * @date 2020/12/9 : 20:16
 */
@ApiModel("学生_课程映射表实体类")
@Entity
@Table(name = "student_discipline_mapping")
@EntityListeners(AuditingEntityListener.class)
@EqualsAndHashCode
@Getter
@Setter
@NoArgsConstructor
public class StudentDisciplineMappingPo {

    /**对应底下的联合主键类**/
    @ApiModelProperty("学生,课程联合主键封装类对象")
    @EmbeddedId
    /**这个注解把StudentDisciplineMpUpk里面的主键成员变量和StudentDisciplineMappingPo表中的字段一一对应起来
       StudentDisciplineMpUpk类的主键成员变量中打上@column注解对应也可以,不过idea工具会爆红,像下面//注释掉的那样*/
    @AttributeOverrides( {
            @AttributeOverride(name = "disIdMp", column = @Column(name = "pk_dis_id_mp")),
            @AttributeOverride(name = "stuIdMp", column = @Column(name = "pk_stu_id_mp")),
            @AttributeOverride(name = "years", column = @Column(name = "pk_years")),
            @AttributeOverride(name = "semester", column = @Column(name = "pk_semester")),
    })
    private StudentDisciplineMpUpk studentDisciplineMpUpk = new StudentDisciplineMpUpk();

    @ManyToOne(fetch = FetchType.LAZY ,cascade = CascadeType.PERSIST)
    @JoinColumn(name = "pk_stu_id_mp", referencedColumnName = "pk_stu_id", insertable=false, updatable=false)
    private StudentPo studentPo;

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
    @JoinColumn(name = "pk_dis_id_mp", referencedColumnName = "pk_dis_id", insertable = false, updatable = false)
    private DisciplinePo disciplinePo;


    @ApiModelProperty("某学生某课程成绩")
    @Column(name = "grade")
    private BigDecimal grade;

    public StudentDisciplineMappingPo(StudentDisciplineMpUpk studentDisciplineMpUpk) {
        this.studentDisciplineMpUpk = studentDisciplineMpUpk;
    }
}

联合主键类一定要实现序列号接口,一定要重写hashcode和equals方法,一定要有无参构造方法

/**
 * @author heChangSheng
 * @date 2020/12/9 : 21:30
 */
@ApiModel("学生课程映射表联合主键封装表")
@Embeddable
@Accessors(chain = true)
@Data
public class StudentDisciplineMpUpk implements Serializable {

    @ApiModelProperty("课程主键id")
    /**引用类中使用@AttributeOverrides注解已经做过映射了*/
   // @Column(name = "pk_dis_id_mp")
    private Long disIdMp;

    @ApiModelProperty("学生主键id")
   // @Column(name = "pk_stu_id_mp")
    private Long stuIdMp;

    @ApiModelProperty("学生学习课程年份")
    //@Column(name = "pk_years")
    private String years;

    @ApiModelProperty("学生学习课程学期")
   // @Column(name = "pk_semester")
    private Short semester;
    
}

二.jpa使用自定义类接收返回数据库返回值

自定义类DisciplineGradeDao(需要接收上面的disciplinePo和studentDisciplineMp映射表中的数据)

自定义类一定要有无参构造,以及对应的接受返回值个数的有参构造(例如sql返回值中只有grade,则一定要有但grade的构造器),且自定义查询语句中的查询字段的顺序一定要和自定义实体的构造方法中的属性顺序一致。

/**
 * @author heChangSheng
 * @date 2020/12/10 : 17:34
 */
@ApiModel("成绩和学科查询封装类")
@ToString
@Data
@NoArgsConstructor
public class DisciplineGradeDao implements Serializable {

    @ApiModelProperty("成绩")
    private BigDecimal grade;

    @ApiModelProperty("学科名称")
    private String disName;

    public DisciplineGradeDao(BigDecimal grade, String disName) {
        this.grade = grade;
        this.disName = disName;
    }

    public DisciplineGradeDao(BigDecimal grade) {
        this.grade = grade;
    }
}

利用jpql语句,将返回值用自定义的类包裹起来,注意使用类全名

public interface StudentDisciplineMappingRepository extends
        JpaRepository<StudentDisciplineMappingPo, StudentDisciplineMpUpk>,
        JpaSpecificationExecutor<StudentDisciplineMappingPo> {

    /**
     * 根据学生id查询学生本人每学年各学科成绩
     *
     * @param stuId
     * @param years
     * @return
     */
    @Query(value = "select 
           /**new 自定义类(查询返回字段)*/
           "new com.nhsoft.module_base.pojo.dao.DisciplineGradeDao(sdm.grade,d.disName)
           "from StudentDisciplineMappingPo sdm left join DisciplinePo d on\n" +
            "sdm.studentDisciplineMpUpk.disIdMp = d.disId where sdm.studentDisciplineMpUpk.stuIdMp = ?1 and sdm.studentDisciplineMpUpk.years = ?2")
    public List<DisciplineGradeDao> listSubjectGradeByYears(Long stuId, String years);
        }

也可以利用sql语句来写,不过比较麻烦,详见笔记

三.使用criteriaquery动态查询

   @Resource
    private EntityManager entityManager;

 /**测试环境下一定要加上事务,因为jpa查询是依赖于spring的事务管理器的*/
    @Test
    @Transactional
    void contextLoads() {


  /**将上面的jpql语句用动态查询criteria实现*/
         /**
          select new com.nhsoft.module_base.pojo.dao.TeacherGradeDao(d.disName,avg(sdm.grade) ,max(sdm.grade),min(sdm.grade))
from StudentDisciplineMappingPo sdm left join DisciplinePo d on sdm.studentDisciplineMpUpk.disIdMp = d.disId
where sdm.studentDisciplineMpUpk.disIdMp = ?1 
      and sdm.studentDisciplineMpUpk.years = ?2
         */

        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        
        //CriteriaQuery<?>中的泛型就是我们要接受sql返回字段的对象,可以是自定义对象
        CriteriaQuery<DisciplineGradeDao> query = criteriaBuilder.createQuery(DisciplineGradeDao.class);
        
        //相当于sql的from 表名, StudentDisciplineMappingPo就是表的po实体类
        //root就相当于from StudentDisciplineMappingPo sdm中的sdm,
        //所以root.get("id") <==> sdm.id
        Root<StudentDisciplineMappingPo> root = query.from(StudentDisciplineMappingPo.class);
        
        //参数一:disciplinePo,from表StudentDisciplineMappingPo中disciplienPo表所对应的成员变量
        //参数二:加入方法,左连接
        //返回的disciplinePoJoin就相当于join DisciplinePo d中的别名d,具体看query.multiselect
        Join<Object, Object> disciplinePoJoin = root.join("disciplinePo", JoinType.LEFT);
        
        //sql语句where后面的条件一:where sdm.studentDisciplineMpUpk.disIdMp = 1 
        Predicate disId = criteriaBuilder.equal(root.get("studentDisciplineMpUpk").get("stuIdMp"), 1L);
        
        //条件二:sdm.studentDisciplineMpUpk.years = 2
        Predicate years = criteriaBuilder.equal(root.get("studentDisciplineMpUpk").get("years"), "2020");
        
        //where语句后面两个条件中间的and:条件一 and 条件二
        Predicate and = criteriaBuilder.and(disId, years);
      
        //sql语句中的select sdm.grade,d.disName;一定要和上面自定义的类(如果是自定义的话,不是就不需要)一一顺序对应
        query.multiselect(
                root.get("grade"), //相当于sdm.grade
                disciplinePoJoin.get("disName") //相当于d.disName
            
            //求出最大值,最小值,使用criteriaBuilder调出聚合函数
              //criteriaBuilder.avg(root.get("grade")),
              //criteriaBuilder.max(root.get("grade")),
              //criteriaBuilder.min(root.get("grade"))
            
            //如果自定义类中的字段名和sql返回字段名不一致,使用alias取别名
           // root.get("grade").as(String.class).alias("mygrade"),
        );
        
        //sql语句中的where xx = xx;disId在上面定义了条件
        query.where(disId);
        
        //利用criteriaquery返回查询结果集
        List<DisciplineGradeDao> resultList = entityManager.createQuery(query).getResultList();
        
        for (DisciplineGradeDao disciplineGradeDao : resultList) {
            System.err.println(disciplineGradeDao);
        }

    }

四.jpa常用注解及注意问题

注解

1.@jsonignore
使用jackson在json序列化时将java bean中的一些属性忽略掉,序列化和反序列化都受影响。修饰属性

2.@EmbeddedId 和 @Embeddable
@Embeddable注释,表示一个类A可以被插入某个entity中,这个类不是数据库表的映射类,加载类上
@EmbeddedId表示这个类A类型成员变量,作为数据库映射类的联合主键,用在数据库映射类中
@Embedded表示这个类A类型成员变量,是数据库表中某几个字段的封装类,如Name name是字段firstName和lastName的封装类,用在数据库映射类中

3.@AttributeOverrides和 @AttributeOverride
一般和2的注解一起使用,表示@Embeddable类中属性和数据库映射表中字段映射
@AttributeOverrides里面包含多个@AttributeOverride
格式@AttributeOverrides({ @AttributeOverride( ),@AttributeOverride( )})
@AttributeOverride(name = “字段名”,column=@column(“数据库表中字段”)

4.@transient
一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
jpa中表示数据库映射类的被修饰变量和数据库表字段不做映射

问题

多对多时,使用@manyToMany的两个类,使用tostring方法打印时一定要注意,有一方不能打印对应类在本类中对应的成员变量,不然会造成栈溢出(死循环,我中打印你,你中打印我,套娃)

五.复杂查询实例

CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();     //查询结果所需要的类型(Entity相对应)
        CriteriaQuery<Entity> criteriaQuery = criteriaBuilder.createQuery(Entity.class);     //查询所需要的主体类(Entity0相对应)
        Root<Entity0> root = criteriaQuery.from(Entity0.class);     //查询结果-select(此处查询所有符合条件的主体类)
        criteriaQuery.select(root);     //过滤条件用Predicate方法拼接
        Predicate restrictions = criteriaBuilder.conjunction();     //过滤条件——equal(当Entity0关联member类时,Entity0:member=m:1)
        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.equal(root.get("member"), member));
        //过滤条件——like
        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.like(root.<String>get("str"), "%"+str+"%"));
        //用户名查询(member里面的username匹配) ———— 多层查询 ———— 子查询的一种:适用于m:1或1:1(即多对一或一对一)
        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.like(root.get("member").<String>get("username"), "%"+username+"%"));
        //子查询(规范写法,先判断查询内容是否存在)(适用于1:m)(即一对多)
        if (searchType != null || searchValue != null || hasExpired != null || status != null || type != null || isPendingReceive != null || isPendingRefunds != null || isAllocatedStock != null || businessType != null) {
            //建立子查询        Subquery<Order> orderSubquery = criteriaQuery.subquery(Order.class);
            Root<Order> orderSubqueryRoot = orderSubquery.from(Order.class);
            orderSubquery.select(orderSubqueryRoot);       //子查询和父查询相关联
            Predicate orderRestrictions = criteriaBuilder.equal(orderSubqueryRoot.<MergeOrder>get("mergeOrder"), root);
            //子查询过滤条件拼接
            if (searchType != null && searchValue != null) {if ("phone".equals(searchType)) {
                    orderRestrictions = criteriaBuilder.and(orderRestrictions, criteriaBuilder.like(orderSubqueryRoot.<String>get("phone"), "%"+searchValue+"%"));
                }
            }if (type != null) {
                CriteriaBuilder.In<Order.Type> in = criteriaBuilder.in(orderSubqueryRoot.<Order.Type>get("type"));
                in.value(type);
                orderRestrictions = criteriaBuilder.and(orderRestrictions, in);
            }
            //and、or以及判断是否为null,比较(>)的使用(比较可以用于日期比较)
            if (hasExpired != null) {
                orderRestrictions = criteriaBuilder.and(orderRestrictions, criteriaBuilder.or(orderSubqueryRoot.get("expire").isNull(), criteriaBuilder.greaterThan(orderSubqueryRoot.<Date>get("expire"), new Date())));
            }
            // not的使用方法(不符合上述过滤条件),notEqual的使用,<(小于)的使用
            if (isPendingReceive != null) {
                restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.equal(root.get("paymentMethodType"), PaymentMethod.Type.cashOnDelivery));
                Predicate predicate = criteriaBuilder.and(criteriaBuilder.or(orderSubqueryRoot.get("expire").isNull()
                        , criteriaBuilder.greaterThan(orderSubqueryRoot.<Date>get("expire"), new Date()))
                        , criteriaBuilder.notEqual(orderSubqueryRoot.get("status"), Order.Status.completed)
                        , criteriaBuilder.lessThan(orderSubqueryRoot.<BigDecimal>get("amountPaid"), orderSubqueryRoot.<BigDecimal>get("amount")));
                if (isPendingReceive) {
                    orderRestrictions = criteriaBuilder.and(orderRestrictions, criteriaBuilder.not(predicate));
                }
            }// 多层查询使用if (businessType != null) {
                orderRestrictions = criteriaBuilder.and(orderRestrictions, criteriaBuilder.equal(orderSubqueryRoot.get("store").get("business").get("businessType"), businessType));
            }       // 拼接过滤条件
            orderSubquery.where(orderRestrictions);
            // 和总条件拼接(exists的使用)
            restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.exists(orderSubquery));
        }
        criteriaQuery.where(restrictions);     TypedQuery<Entity> query = entityManager.createQuery(criteriaQuery);     Entity singleResult = query.getSingleResult();
  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
JPA使用原生 SQL 进行查询时,实体字段赋值可以通过使用 @SqlResultSetMapping 和 @ConstructorResult 注解来实现。 @SqlResultSetMapping 注解用于定义结果集的映射关系,可以指定多个实体和列名的映射关系。 @ConstructorResult 注解用于指定实体的构造函数参数与列名的映射关系。 以下是一个使用原生 SQL 进行查询的示例: ```java @SqlResultSetMapping( name = "UserGroupMapping", classes = @ConstructorResult( targetClass = UserGroupDto.class, columns = { @ColumnResult(name = "userId", type = Long.class), @ColumnResult(name = "userName", type = String.class), @ColumnResult(name = "groupId", type = Long.class), @ColumnResult(name = "groupName", type = String.class) } ) ) public interface UserDao extends JpaRepository<User, Long> { @Query( value = "SELECT u.id AS userId, u.name AS userName, g.id AS groupId, g.name AS groupName " + "FROM user u LEFT JOIN user_group ug ON u.id = ug.user_id " + "LEFT JOIN group g ON ug.group_id = g.id " + "WHERE u.id = :userId", nativeQuery = true, resultSetMapping = "UserGroupMapping" ) UserGroupDto getUserGroup(@Param("userId") Long userId); } ``` 在上面的示例中,我们定义了一个名为 UserGroupMapping 的映射关系,将多个实体和列名的映射关系定义在了 @ConstructorResult 注解中。在 UserDao 接口中,我们使用 @Query 注解指定了原生 SQL 语句,并通过 resultSetMapping 属性将结果集映射到 UserGroupDto 中,从而实现了多查询并将结果封装到一个自定义的 DTO 中。 另外,需要注意的是,如果原生 SQL 查询返回的结果集中包含实体中不存在的字段,那么这些字段的值将不会被赋值给实体的属性。因此,需要确保查询语句中的列名与实体中的属性名一致。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值