zoukankan      html  css  js  c++  java
  • Spring Data JPA实体详解

    1. Spring Data JPA实体概述

    JPA提供了一种简单高效的方式来管理Java对象(POJO)到关系数据库的映射,此类Java对象称为JPA实体或简称实体。实体通常与底层数据库中的单个关系表相关联,每个实体的实例表示数据库表格中的某一行。

    2. Spring Data JPA实体管理器

    2.1 实体管理器概述

    实体管理器(EntityManager)用于管理系统中的实体,它是实体与数据库之间的桥梁,通过调用实体管理器的相关方法可以把实体持久化到数据库中,同时也可以把数据库中的记录打包成实体对象。

    2.2 实体管理器的常用方法

    2.2.1 实体的四种状态

    在此之前我们要先了解实体的状态及其转换,见下图

    JPA实体生命周期有四种状态

    • 新建状态(New):对象在保存进数据库之前为临时状态。此时数据库中没有该对象的信息,该对象的ID属性也为空。如果没有被持久化,程序退出时临时状态的对象信息将丢失。
    • 托管状态(Managed):对象在保存进数据库后或者从数据库中加载后、并且没有脱离Session时为持久化状态。这时候数据库中有对象的信息,改对象的id为数据库中对应记录的主键值。由于还在Session中,持久化状态的对象可以执行任何有关数据库的操作,例如获取集合属性的值等。
    • 游离状态(Datached):是对象曾经处于持久化状态、但是现在已经离开Session了。虽然分离状态的对象有id值,有对应的数据库记录,但是已经无法执行有关数据库的操作。例如,读取延迟加载的集合属性,可能会抛出延迟加载异常。
    • 删除状态(Removed):删除的对象,有id值,尚且和Persistence Context有关联,但是已经准备好从数据库中删除。
    状态名  作为java对象存在  在实体管理器中存在  在数据库存在
    New  Y N N
    Managed Y Y Y
    Datached N N N
    Removed Y Y N

    用一段程序来示范

    @Transactional
    public void save(){
    
        //New 状态
        Task t = new Task();
        t.setTaskName("task" + new Date().getTime());
        t.setCreateTime(new Date());
    
        //Managed状态
        em.persist(t); //实体类t已经有id t.getId();
        t.setTaskName("kkk");  //更新任务名称,这时,如果提交事务,则直接将kkk更新到数据库
    
        //Detached状态 事务提交或者调用em.clear都直接将实体任务状态变为Detached
        em.clear();
        t.setTaskName("kkk"); //更新数据不会更新到数据库
    
        //Removed状态
        em.remove(t);
    }

    2.2.2  实体管理器的常用方法

    对应于实体的四种状态,实体管理器有四种常用的方法,分别是:persist / merge / clear / remove,结合状态图,可以判断,对于不同状态下的实体,各个方法操作结果会有不同:

    对于不同状态下的实体,persist 操作结果如下:

    • 新建状态:实体状态迁移到托管状态
    • 托管状态:实体状态不发生改变,但会执行数据库的insert操作
    • 游离状态:方法的调用将会抛出异常信息
    • 删除状态:实体将重返托管状态

    对于不同状态下的实体,merge 操作结果如下:

    • 新建状态:系统会执行数据库insert操作,同时返回一个托管状态的实体
    • 托管状态:实体状态不发生改变
    • 游离状态:系统将实体的修改保存到数据库,同时返会一个托管状态的实体
    • 删除状态:方法调用将抛出异常

    对于不同状态下的实体,refresh 操作结果如下:

    • 新建状态:系统会执行数据库insert操作,同时返回一个托管状态的实体
    • 托管状态:实体状态不发生改变,但会执行数据库的update操作
    • 游离状态:实体状态将返回托管状态
    • 删除状态:方法调用将抛出异常

    对于不同状态下的实体,remove 操作结果如下:

    • 新建状态:方法调用将抛出异常
    • 托管状态:实体状态变成删除状态
    • 分离状态:方法调用将抛出异常
    • 删除状态:不发生任何操作

    2.2.3 利用实体管理器管理实体(实现实体的CURD)

    public class UserRepositoryImpl {
    
        @PersistenceContext
        private EntityManager entityManager;
        
        @Transactional
        public void add(User user) {
            entityManager.persist(user);
    
        }
        @Transactional
        public User update(User user) {
            User userUpdate = entityManager.find(User.class, user.getId());
            userUpdate.setAddress(user.getAddress());
            userUpdate.setName(user.getName());
            userUpdate.setPhone(user.getPhone());    
            return userUpdate;
        }
        @Transactional
        public User addOrUpdate(User user) {    
            return entityManager.merge(user);
        }
        @Transactional
        public void delete(User user) {
            entityManager.remove(user);
        }
    
        public User findOne(Integer id) {   
            return entityManager.find(User.class, id);
        }
    
        public List<User> findAll() {
            String queryString = "select u from User u";
            Query query = entityManager.createQuery(queryString);
            return query.getResultList();
        }
    }

    3. Spring Data JPA实体基础映射

    3.1 表映射

    @Entity //表示该类为JPA实体类
    @Table(name="t_user")   //对应数据库中哪张表
    public class User {
    
    @Column(name="phone_", length=11, nullable=true,columnDefinition="CHAR(10) default '000'")  //对应数据库表中哪个列字段及对该字段的自定义
        private String phone;

    3.2 主键映射

    @Id   //标明主键
    @GeneratedValue   //主键生成策略
    @Column(name="id_")
    private Integer id;    

    更多的主键生成策略,详见3.6 的总体代码

    3.3 字段映射和约束条件

    @Column(name="phone_", length=11, nullable=true,columnDefinition="CHAR(10) default '000'")  //对应数据库中哪个列及对该字段的自定义
        private String phone;

    3.4 单实体多表格存储

    通常一个实体对应于一个表格,即表格中的所有的实体属性都存放于一张表,如果将实体的属性分配到多个表格存放,就涉及到单实体多表格存储

    @Entity
    @Table(name="t_user",catalog="",schema="")
    @SecondaryTables({    //指明存放的第二张表
        @SecondaryTable(name = "t_address",pkJoinColumns=@PrimaryKeyJoinColumn(name="address_id")) 
     })
    public class User {
        
        @Column(name="name_", length=60, nullable=false,unique=true,insertable=false)
        private String name;
        
        //分表存储
        @Column(table = "t_address", name="street_", length = 100) 
        private String street;

    3.5 内嵌实体

    在定义实体时可能需要将某几个的属性剥离出放到另外一个实体中,以使程序更有层次感,并且当其他实体也需要这几个属性时,我们也不需要再定义这几个属性,把存放这几个属性的实体重新引用即可,操作方法如下:

    @Embeddable   //标识该实体可嵌入到其他实体中
    public class Comment {
    @Column(name="title_",length=100)
    String title;
    @Column(name="content_")
    String content;
    /* //被剥离出的属性
    @Column(name="title_",length=100)
        String title;
    @Column(name="content_")
        String content;   
    */    
    @Embedded   //引入该实体
    @AttributeOverrides({   //罗列出所有需要重新命名的属性
        @AttributeOverride(name = "title", column = @Column(name = "user_title")),
        @AttributeOverride(name = "content", column = @Column(name = "user_content"))
      })
        private Comment comment; 

    内嵌实体在数据库中不会一点单独的表格存放,而是跟数组实体存放于同一表格中。

    3.6 实体类代码

    import java.math.BigDecimal;
    import java.util.Date;
    import javax.persistence.*;
    import org.hibernate.annotations.GenericGenerator;
    
    @Entity
    @Table(name="t_user",catalog="",schema="")
    @SecondaryTables({ 
        @SecondaryTable(name = "t_address",pkJoinColumns=@PrimaryKeyJoinColumn(name="address_id")) 
     })
    public class User {
    
        @Id   //标明主键
        @GeneratedValue   //主键生成策略
        @Column(name="id_")
        private Integer id;    
        
    /*    @Id
        @GeneratedValue(generator="uuidGenerator")
        @GenericGenerator(name="uuidGenerator",strategy="uuid")
        @Column(name="id_",length=32)
        private String id;*/    
        
    /*    @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        @Column(name="id_")
        private Integer id;*/    
            
        /*
         *     
        @Id
        @GeneratedValue(strategy=GenerationType.IDENTITY)
        @Column(name="id_")
        private Integer id;*/
        
        /*    
        @Id
        @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "idGenerator")
        @SequenceGenerator(name = "idGenerator",sequenceName="mySeq",allocationSize=1)
        @Column(name="id_")
        private Integer id;*/    
        
        /*    
        @Id
        @GeneratedValue(strategy = GenerationType.TABLE, generator = "userGenerator")
        @TableGenerator(name = "userGenerator",table="pk_generator",
                        pkColumnName="gen_name",
                        valueColumnName="gen_value",
                        pkColumnValue="user_pk",
                        initialValue=0,
                        allocationSize=1)
        @Column(name="id_")
        private Integer id;    
        */
        
        @Column(name="name_", length=60, nullable=false,unique=true,insertable=false)
        private String name;
        
        @Column(name="address_", length=60, nullable=false)
        private String address;
        
        @Column(name="phone_", length=11, nullable=true,columnDefinition="CHAR(10) default '000'")
        private String phone;
        
        @Column(name="inCome_", precision=12, scale=2)
        private BigDecimal inCome;
        
        @Temporal(TemporalType.DATE)
        private Date birthday;
        //Date 日期型,精确到年月日,例如“2008-08-08”
        //Time 时间型,精确到时分秒,例如“20:00:00”
        //Timestamp 时间戳,精确到纳秒,例如“2008-08-08 20:00:00.000000001”
        
        @Lob
        @Column(name="pic_")
        @Basic(fetch=FetchType.LAZY)
        private byte[] pic;
        
        @Lob
        @Column(name="note_")
        @Basic(fetch=FetchType.LAZY)
        private String note;
            
    
        //分表存储
        @Column(table = "t_address", name="street_", length = 100) 
        private String street;
        //分表存储
        @Column(table = "t_address", name="city_") 
        private String city; 
        //分表存储
        @Column(table = "t_address", name="conutry_",length = 20) 
        private String conutry; 
        
        @Column(name="title_",length=100)
        String title;
        @Column(name="content_")
        String content;
        
        /*    
        @Embedded   //引入该实体
        @AttributeOverrides({   //罗列出所有需要重新命名的属性
            @AttributeOverride(name = "title", column = @Column(name = "user_title")),
            @AttributeOverride(name = "content", column = @Column(name = "user_content"))
            })
        private Comment comment; 
        
        */
          
    //省略get/set方法
    }

    4. Spring Data JPA实体高级映射

    4.1 一对一实体映射的概念和实现方法

    如下例,人员表(person)和地址表(adddress),person表是关系的拥有者,表中的address_id字段关联着address表的主键id。

    @Entity
    public class Person {
      //
      @OneToOne   @JoinColumn(name="address_id",referencedColumnName="aid")//name:主表的外键字段; referencedColumnName:从表的主键   //如果关联的字段有多个,采用如下注解   //@JoinColumns(value={@JoinColumn(name="address_id",referencedColumnName="aid"),@JoinColumn(name="address_id2",referencedColumnName="aid2")})    private Address address;

    4.2 一对多实体映射的概念和实现方法

    部门表(depart)和员工表(employee),一个部门可以有多个员工,一对多关系可以采用如下两种实现方法。

    4.2.1 中间表方式

    创建中间表(depart_employee),表中存放两个表的主键。通过部门id可查询关联员工的id,三张表存在两个主外键关系。

    @Entity
    public class Depart {
        //
    
       @OneToMany
        @JoinTable(name = "depart_employee", //name:关联表
            joinColumns = @JoinColumn(name = "depart_id",referencedColumnName="did"),  //joinColumns:关系的拥有者与关联表的关系
            inverseJoinColumns = @JoinColumn(name = "employee_id",referencedColumnName="eid"))//inverseJoinColumns:关系的被拥有者与关联表的关系
        private List<Employee> employees;

    4.2.2 从表增加外键方式

    在员工表(employee2)中添加一个depart_id字段,它作为外键关联部门表(depart2)的主键id。

    @Entity
    public class Depart2 {
      //
        
        @OneToMany
        @JoinColumn(name="depart_id",referencedColumnName="id")
        private List<Employee2> employee2s;

    4.3 多对多实体映射的概念的实现方法

    多对多的实现也是通过中间表,方法同一对多的中间表实现方式。

     

    @Entity
    public class Teacher {
    //
    
        @ManyToMany 
        @JoinTable(name = "teacher_student", 
            joinColumns = @JoinColumn(name = "teacher_id",referencedColumnName="tid"), 
            inverseJoinColumns = @JoinColumn(name = "student_id",referencedColumnName="sid"))
        private List<Student> students;
        
    @Entity
    public class Student {
    //
        
        @ManyToMany(mappedBy = "students") 
        private List<Teacher> teachers; 

    4.4 级联策略和懒加载

    以@OneToOne为例,当我希望删除人员信息时,也将其地址信息删除,则可使用级联策略;当我想要查询人员信息(主实体)时,并不想同时查询出其地址信息(子实体),可以设置懒加载。

    @Entity
    public class Person {
    @OneToOne(cascade={CascadeType.REFRESH,CascadeType.REMOVE},fetch=FetchType.LAZY)
    //@JoinColumn(name="address_id",referencedColumnName="aid")
      private Address address;   

    5. Spring Data JPA实体继承

    5.1 实体继承的概念

    继承[extends]想必已不陌生,对于JPA来说,我们不但要考虑如何实现Java端的继承关系,还要考虑如何持久化到数据库中。JPA为此提供了三种策略,如下:

    5.2 实体继承策略

    继承关系如图,继承策略的注解主要应用于父类Item。

    5.2.1 继承策略之单一表策略

    @Entity
    @Inheritance(strategy = InheritanceType.SINGLE_TABLE) 
    public class Item {

    执行单一表策略会将所有实体的信息存放于一张表中,它的优点是信息存放于一张表,查询效率较高,缺点是大量字段为空,浪费存储空间。

    如果类名过长或需要更改鉴别字段的名称,可对鉴别字段及可选值自定义:

    @Entity
    @Inheritance(strategy = InheritanceType.SINGLE_TABLE) 
    @DiscriminatorColumn(name="ITYPE",discriminatorType=discriminatorType.CHAR) //声明鉴别字段的字段名,类型
    @DiscriminatorValue("I") //该表在鉴别字段列显示的值
    public class Item {
    @Entity
    @DiscriminatorValue("P") 
    public class Phone extends Item {
    @Entity
    @DiscriminatorValue("B") 
    public class Book extends Item {

    效果如下

    5.2.2 继承策略之连接表策略

    @Entity
    @Inheritance(strategy = InheritanceType.JOINED) 
    public class Item {

     连接表策略会生成三张表,通过共享主键彼此关联。

     这种策略避免了空字段的浪费,但由于采用表关联查询,当数据量过大时,查询效率较低。

    5.2.3 继承策略之每个类策略

    @Entity
    @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) 
    public class Item {
    /*    @Id 
        @GeneratedValue(strategy = GenerationType.AUTO) */
      
      @Id    
      @GeneratedValue(strategy = GenerationType.TABLE, generator = "ItemGenerator")
      @TableGenerator(name = "ItemGenerator",table="pk_generator",
                        pkColumnName="gen_name",
                        valueColumnName="gen_value",
                        pkColumnValue="item_pk",
                        initialValue=0,
                        allocationSize=1)
        private Long id; 

    每个类策略实际上是每个类一个表策略,这种策略要求主键不能使用自增的方式,如上面的代码,采用表中获取的方式。

    三张表各自存放自己的完整信息,表之间没有任何的关联关系。虽然他们各自存放各自的数据,但主键是连续的。即三个表共用一套主键生成策略(三个表的主键都从另一个表中获取)。

    这种策略查询效率高,同时也不存在大量空字段的浪费。

  • 相关阅读:
    需要学习的技术
    面试资料
    数据库设计三大范式
    java List 排序 Collections.sort() 对 List 排序
    hibernate的延迟加载
    索引失效原因总结
    mybatis调用oracle存储过程
    Android开发中需要注意哪些坑
    Intent在Activity之间传值的几种方式
    Android动画(Animations)
  • 原文地址:https://www.cnblogs.com/zjfjava/p/9064092.html
Copyright © 2011-2022 走看看