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

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

①表结构

学生表(student)

字段名

字段中文

类型

描述

pk_stu_id

学生id

long

主键

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();