A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

Specifications动态查询
  • 有时我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句
  • 在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询
  • 相比JPQL,其优势是类型安全,更加的面向对象
使用Specifications完成条件查询@Testpublic void testSpecifications() {    "使用匿名内部类的方式,创建一个Specification的实现,并实现toPredicate方法"    Specification<Customer> spec = new Specification<Customer>() {        @Override        public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {            "cb:构建查询,添加查询方式   like:模糊匹配"            "root:从实体Customer对象中按照custName属性进行查询"            Expression<String> custName = root.get("custName").as(String.class);            Predicate predicate = cb.like(custName, "余%");            return predicate;        }    };    List<Customer> list = customerDao.findAll(spec);    for (Customer customer : list) {        System.out.println(customer.getCustName());    }}基于Specifications的分页查询@Testpublic void testPage2() {    Specification<Customer> spec = new Specification<Customer>() {        @Override        public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {            Expression<String> custName = root.get("custName").as(String.class);            Predicate predicate = cb.like(custName, "%%");            return predicate;        }    };    第一个参数:页码(从0开始)"    第二个参数:每页查询条数"    Pable pageable = new PageRequest(0,1);    "第一个参数:查询条件Specification"    "第二个参数:分页参数"    Page<Customer> list = customerDao.findAll(spec, pageable);    for (Customer c : list) {        System.out.println(c.getCustName());    }}多表设计
  • 系统设计的三种实体关系分别为:多对多、一对多和一对一关系
JPA中的一对多@Entity@Table(name = "cst_customer")public class Customer implements Serializable {    @Id    @Column(name = "cust_id")    @GeneratedValue(strategy = GenerationType.IDENTITY)    private Long custId;    @Column(name = "cust_name")    private String custName;    @Column(name = "cust_source")    private String custSource;    @Column(name = "cust_industry")    private String custIndustry;    @Column(name = "cust_level")    private String custLevel;    @Column(name = "cust_address")    private String custAddress;    @Column(name = "cust_phone")    private String custPhone;    "配置客户和联系人的一对多关系"    @OneToMany(targetEntity= LinkMan.class)    @JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")    private Set<LinkMan> linkmans = new HashSet<LinkMan>(0);        "setter and getter methods"}@Entity@Table(name = "cst_linkman")public class LinkMan implements Serializable {    @Id    @GeneratedValue(strategy = GenerationType.IDENTITY)    @Column(name = "lkm_id")    private Long lkmId;    @Column(name = "lkm_name")    private String lkmName;    @Column(name = "lkm_gender")    private String lkmGender;    @Column(name = "lkm_phone")    private String lkmPhone;    @Column(name = "lkm_mobile")    private String lkmMobile;    @Column(name = "lkm_email")    private String lkmEmail;    @Column(name = "lkm_position")    private String lkmPosition;    @Column(name = "lkm_memo")    private String lkmMemo;    "多对一关系映射:多个联系人对应客户"    @ManyToOne(targetEntity = Customer.class)    @JoinColumn(name = "lkm_cust_id", referencedColumnName = "cust_id")    "用它的主键,对应联系人表中的外键"    private Customer customer;    "setter and getter methods"}
  • 映射的注解说明
    • @OneToMany
      • 作用:建立一对多的关系映射
      • 属性:
        • targetEntityClass:指定多的多方的类的字节码
        • mappedBy:指定从表实体类中引用主表对象的名称
        • cascade:指定要使用的级联操作
        • fetch:指定是否采用延迟加载
        • orphanRemoval:是否使用孤儿删除

    • @ManyToOne
      • 作用:建立多对一的关系
      • 属性:
        • targetEntityClass:指定一的一方实体类字节码
        • cascade:指定要使用的级联操作
        • fetch:指定是否采用延迟加载
        • optional:关联是否可选。如果设置为false,则必须始终存在非空关系

    • @JoinColumn
      • 作用:用于定义主键字段和外键字段的对应关系
      • 属性:
        • name:指定外键字段的名称
        • referencedColumnName:指定引用主表的主键字段名称
        • unique:是否唯一。默认值不唯一
        • nullable:是否允许为空。默认值允许
        • insertable:是否允许插入。默认值允许
        • updatable:是否允许更新。默认值允许
        • columnDefinition:列的定义信息



/** * 当我们建立了双向的关联关系之后,先保存主表,再保存从表时: * 会产生2条insert和1条update. * 而实际开发中我们只需要2条insert。 */@Test@Transactional  //开启事务@Rollback(false)//设置为不回滚public void testAdd() {    Customer c = new Customer();    c.setCustName("Xxx科技股份有限公司");    c.setCustLevel("VIP客户");    c.setCustSource("电线杆");    c.setCustIndustry("商业办公");    c.setCustAddress("广州市天河区");    c.setCustPhone("1234567");    LinkMan l = new LinkMan();    l.setLkmName("xy");
    l.setLkmGender("male");    l.setLkmMobile("15521440000");    l.setLkmPhone("1234567");    l.setLkmEmail("xxxxxxxxx@qq.com");    l.setLkmPosition("咸鱼");    l.setLkmMemo("........");    c.getLinkMans().add(l);    l.setCustomer(c);    customerDao.save(c);    linkManDao.save(l);}
  • 删除主表数据
    • 在默认情况下,它会把外键字段置为null,然后删除主表数据
    • 如果在数据库的表结构上,外键字段有非空约束,默认情况就会报错了
    • 如果配置了放弃维护关联关系的权利,则不能删除,因为在删除时,它根本不会去更新从表的外键字段了
    • 如果还想删除,使用级联删除引用

"先执行一条查询,再update外键,最后删除主表数据"@Test@Transactional@Rollback(false)//设置为不回滚public void testDelete() {    customerDao.delete(9l);}
  • 级联操作
    • 级联操作:指操作一个对象同时操作它的关联对象
  • 使用方法:只需要在操作主体的注解上配置cascade
/** * cascade:配置级联操作 *                 CascadeType.MERGE        级联更新 *                 CascadeType.PERSIST        级联保存: *                 CascadeType.REFRESH 级联刷新: *                 CascadeType.REMOVE        级联删除: *                 CascadeType.ALL                包含所有 */@OneToMany(mappedBy="customer",cascade=CascadeType.ALL)JPA中的多对多@Entity@Table(name="sys_user")public class SysUser implements Serializable {    @Id    @GeneratedValue(strategy=GenerationType.IDENTITY)    @Column(name="user_id")    private Long userId;    @Column(name="user_code")    private String userCode;    @Column(name="user_name")    private String userName;    @Column(name="user_password")    private String userPassword;    @Column(name="user_state")    private String userState;    "多对多关系映射"    @ManyToMany(mappedBy="users")    private Set<SysRole> roles = new HashSet<SysRole>(0);    "setter and getter methods"}@Entity@Table(name = "sys_role")public class SysRole implements Serializable {    @Id    @GeneratedValue(strategy = GenerationType.IDENTITY)    @Column(name = "role_id")    private Long roleId;    @Column(name = "role_name")    private String roleName;    @Column(name = "role_memo")    private String roleMemo;    "多对多关系映射"    @ManyToMany    @JoinTable(name = "user_role_rel",//中间表的名称            "中间表user_role_rel字段关联sys_role表的主键字段role_id"            joinColumns = {@JoinColumn(name = "role_id", referencedColumnName = "role_id")},            "中间表user_role_rel的字段关联sys_user表的主键user_id"            inverseJoinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "user_id")}    )    private Set<SysUser> users = new HashSet<SysUser>(0);        "setter and getter methods"}
  • 映射的注解说明
    • @ManyToMany
      • 作用:用于映射多对多关系
      • 属性:
        • cascade:配置级联操作
        • fetch:配置是否采用延迟加载
        • targetEntity:配置目标的实体类。映射多对多的时候不用写

    • @JoinTable
      • 作用:针对中间表的配置
      • 属性:
        • nam:配置中间表的名称
        • joinColumns:中间表的外键字段关联当前实体类所对应表的主键字段
        • inverseJoinColumn:中间表的外键字段关联对方表的主键字段

    • @JoinColumn
      • 作用:用于定义主键字段和外键字段的对应关系
      • 属性:
        • name:指定外键字段的名称
        • referencedColumnName:指定引用主表的主键字段名称
        • unique:是否唯一。默认值不唯一
        • nullable:是否允许为空。默认值允许
        • insertable:是否允许插入。默认值允许
        • updatable:是否允许更新。默认值允许
        • columnDefinition:列的定义信息


  • 多对多的操作
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations = "classpath:applicationContext.xml")public class ManyToManyTest {    @Autowired    private UserDao userDao;    @Autowired    private RoleDao roleDao;    @Test    @Transactional  //开启事务    @Rollback(false)//设置为不回滚    public void test1(){        //创建对象        SysUser u1 = new SysUser();        u1.setUserName("用户1");        SysRole r1 = new SysRole();        r1.setRoleName("角色1");        //建立关联关系        u1.getRoles().add(r1);        r1.getUsers().add(u1);        //保存        roleDao.save(r1);        userDao.save(u1);    }}
  • 在多对多(保存)中,如果双向都设置关系,意味着双方都维护中间表,都会往中间表插入数据
  • 中间表的2个字段又作为联合主键,所以报错
  • 主键重复,解决保存失败的问题
    • 只需要在任意一方放弃对中间表的维护权即可,推荐在被动的一方放弃,配置如下

"放弃对中间表的维护权,解决保存中主键冲突的问题"@ManyToMany(mappedBy="roles")private Set<SysUser> users = new HashSet<SysUser>(0);@Test@Transactional@Rollback(false)//设置为不回滚public void testDelete() {    userDao.delete(1l);}
  • 删除报错,因为它不维护外键
Spring Data JPA中的多表查询
  • 对象导航查询
    • 对象图导航检索方式是根据已经加载的对象,导航到他的关联对象
    • 它利用类与类之间的关系来检索对象
    • 例如:
      • 我们通过ID查询方式查出一个客户,可以调用Customer类中的getLinkMans()方法来获取该客户的所有联系人
      • 对象导航查询的使用要求是:两个对象之间必须存在关联关系


@Autowiredprivate CustomerDao customerDao;@Test"由于是在java代码中测试,为了解决no session问题,将操作配置到同一个事务中"@Transactional public void testFind() {        Customer customer = customerDao.findOne(5l);        Set<LinkMan> linkMans = customer.getLinkMans();//对象导航查询        for(LinkMan linkMan : linkMans) {                  System.out.println(linkMan);        }}"查询一个联系人,获取该联系人的所有客户"@Autowiredprivate LinkManDao linkManDao;@Testpublic void testFind() {    LinkMan linkMan = linkManDao.findOne(4l);    Customer customer = linkMan.getCustomer(); //对象导航查询    System.out.println(customer);}
  • 问题1:我们查询客户时,要不要把联系人查询出来?
    • 分析:如果我们不查的话,在用的时候还要自己写代码,调用方法去查询
    • 如果我们查出来的,不使用时又会白白的浪费了服务器内存
  • 解决:采用延迟加载的思想。通过配置的方式来设定当我们在需要使用时,发起真正的查询
/** * 在客户对象的@OneToMany注解中添加fetch属性 *                 FetchType.EAGER        :立即加载 *                 FetchType.LAZY        :延迟加载 */@OneToMany(mappedBy="customer",fetch=FetchType.EAGER)private Set<LinkMan> linkMans = new HashSet<>(0);
  • 问题2:我们查询联系人时,要不要把客户查询出来?
    • 分析:例如:查询联系人详情时,肯定会看看该联系人的所属客户
    • 如果我们不查的话,在用的时候还要自己写代码,调用方法去查询
    • 如果我们查出来的话,一个对象不会消耗太多的内存
    • 而且多数情况下我们都是要使用的
  • 解决:采用立即加载的思想,通过配置的方式来设定,只要查询从表实体,就把主表实体对象同时查出来
/** * 在联系人对象的@ManyToOne注解中添加fetch属性 *                 FetchType.EAGER        :立即加载 *                 FetchType.LAZY        :延迟加载 */@ManyToOne(targetEntity=Customer.class,fetch=FetchType.EAGER)@JoinColumn(name="cst_lkm_id",referencedColumnName="cust_id")private Customer customer;

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马