JPA入门

来源:互联网 发布:淘宝仓管工作职责 编辑:程序博客网 时间:2024/05/16 04:03

                                                JPA入门

                                                        -----看了黎活明老师视频后总结的学习笔记(有些摘于网上,经过整理以及修改和补充)

一、什么是JPA?

JPA是sun官方提出的java持久化规范。他为java开发人员提供了一种对象/关系映射工具来管理java应用中的关系数据。他的出现主要是为了规范现有的持久层开发工作和整合ORM技术,结束现在hibernate、toplink,JDO等ORM框架各自为营的局面。值得注意的是,JPA是在充分吸收了现有hibernate、toplink,JDO等ORM框架的基础上发展而来的,具有易于使用、伸缩性强等优点,从目前的开发社区的反应上看,JPA收到了极大的支持和赞扬,其中就包括了Spring与EJB3.0的开发团队。着眼未来几年的技术走向,JPA作为ORM领域标准化整合者的目标应该不难实现。

   简单的说就是持久层的一套API接口(规范),所有的持久层框架的方法都是这套API的具体实现(不包括其自身的扩展)。但是我们面对的就一套API。具体实现由持久层服务商提供。跟JDBC接口规范是一样的,具体的数据库厂商去实现JDBC接口。

   JPA包含以下3方面的技术:

.ORM映射元数据

JPA支持XML和JDK5.0注释(也可译作注解)两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此将尸体对象持久化到数据库表中。

.Java持久化API

用来操作实体对象,执行CRUD操作,框架在后台替我们完成所有的事情,开发者可以从繁琐的JDBC和SQL代码中解脱出来。

.查询语言

这是持久化操作中很重要的一个方面,通过面向对象而非面向数据库的查询语言查询数据,避免程序的SQL语句紧密耦合。


.与其他ORM框架的关系

JPA不是一种新的ORM框架,他的出现只是用于规范现有的ORM技术,他不取代现有的HIbernate、TopLink等ORM框架。相反,在采用JPA开发时,我们仍将使用这些框架作为具体实现。这样我们的持久层就不会再依赖某个持久化提供商。应用可以在不修改代码的情况下在任何JPA环境下运行,真正做到低耦合,可扩展的程序设计。


二、JPA开发环境和思想介绍

导入的jar:

Hibernate核心包:

hibernate-distribution-3.6.0.Final下的hibernate3.jar、lib\bytecode\cglib\cglib-2.2.jar、lib\required\*.jar、lib\jpa\hibernate-jpa-2.0-api-1.0.0.Final.jar

mysql驱动包:mysql-connector-java-5.1.12.jar

日志包:slf4j-api-1.7.2.jar、slf4j-simple-1.7.5.jar或者

slf4j-api,slf4j-log4j12、log4j等其它日志组件。


开发思想:传统的数据建模,先建表,然后再根据表来编写配置文件和实体bean,但是JPA使用采用领域建模思想即先编写配置文件和实体bean,然后再生成表。


三、搭建JPA开发环境和全局事务介绍

Persistence.xml:

JPA规范要求在类路径的META-INF目录下放置persistence.xml,文件的名称是固定的。配置详解如下

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence" >http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
    <persistence-unit name="MYSQL" transaction-type="RESOURCE_LOCAL">
      <!--

name持久化单元名称(自定义),说明可以配置多个持久化单元。
         指定具体实现,这里是hibernate,可省
 <provider>org.hibernate.ejb.HibernatePersistence</provider>
-->
<!--
    jpa也有一套映射配置方式,比hibernate还要复杂所以我们不用,一般是用注解的方式
        <mapping-file>
        </mapping-file>
-->
        <properties>
            <property name="hibernate.connection.username" value="root"/>
            <property name="hibernate.connection.password" value=" 123"/>
             <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect" />
            <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
            <property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/jpadata"/>
            <property name="hibernate.hbm2ddl.auto" value="update"/>
            <property name="show_sql" value="true"/>
        </properties>
    </persistence-unit>
</persistence>

 

transaction-type(事务类型):

JTA和RESOURCE_LOCAL,即Java Transaction API方法和本地的事务管理。

JTA针对多种数据库一起使用的情况,比如同时往mysql和oracle更新数据必须放在一个事务中,支持分布式的事务。

RESOURCE_LOCAL只能针对一种数据库,不支持分布式的事务。

 

四、第一个JPA实例与JPA主键生成策略


@Entity //定义为映射实体
@Table(name = "user") //确定数据库表名
public class User {
     private Integer id;
    private String name;

//hibernate的bean创建需要用到无参构造函数,所以一定要提供//无参构造函数
       public User() {
    }
    public User(String name) {
        this.name = name;
    }
//指定主键字段,并声明id策略,
    @Id @GeneratedValue(strategy = GenerationType.AUTO)
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

JPA提供的id生成策略:

IDENTITY:mysql、sqlserver等使用的(数值型)

SEQUENCE:oracle使用的(数值型)

TABLE:所有数据库通用的(数值型)

AUTO:根据数据库类型判断使用IDENTITY还是SEQUENCE,默认值

可省略strategy = GenerationType.AUTO

不支持uuid或者字符串类型的id。如果是字符类型的需要自己手动赋值。

注意:

注解写在get方法上和属性上都可以。

数值型主键查询比字符型主键查询性能更好。


测试:

@Test
public void save(){

//在获取factory时就会创建表,参数MYSQL为<persistence-unit //name="MYSQL" transaction-type="RESOURCE_LOCAL">中的name值
EntityManagerFactory factory = Persistence.createEntityManagerFactory("MYSQL");

//创建实体管理对象,相当于hibernate中的session
       EntityManager em = factory.createEntityManager();
        em.getTransaction().begin();
       em.persist(new User("xinyanfang"));
        em.getTransaction().commit();
        em.close();
        factory.close();
   }

表结构:

 


五、日期_枚举等字段类型的JPA映射

@Column:

Length指定数据库字段值的长度

Nullable是否为空

Name 列名

@Temporal:

TemporalType.DATE   指定为日期类型

TemporalType.TIME   指定为时间类型

TemporalType.TIMESTAMP  指定为日期时间类型

@Enumerated:

EnumType.STRING  指定数据库中存储的值为枚举值(MAN,WOMEN)

EnumType.ORDINAL 指定数据库中存储的值为索引值(MAN的索引值0,WOMEN索引值1)



Gender枚举类

public  enum  Gender {
      MAN,WOMEN
}

User类


@Entity //定义为映射实体
@Table(name = "user") //确定数据库表名
public class User {
     private Integer id;
    private String name;
    private Date birthday;

//设置默认值
    private Gender gender = Gender.MAN;
    public User() {
    }

//枚举类型不能为空,所以非空约束一定要加上
    @Enumerated(EnumType.STRING) @Column(length = 5,nullable = false)
    public Gender getGender() {
        return gender;
    }
    public void setGender(Gender gender) {
        this.gender = gender;
    }
    @Temporal(TemporalType.DATE)
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    public User(String name) {
        this.name = name;
    }
    //指定主键字段,并声明id策略
    @Id @GeneratedValue()
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    @Column(length = 10,nullable = false,name = "userName")
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}


表结构

 

 

 

六、大数据字段映射与字段延迟加载

@Lob 声明字段为大数据类型(包括大文本和视频音频等二进制文件),会根据类中属性的类型在数据库中会自动区分,生成LONGBLOB、LONGTEXT。

@Transient 当前字段不会映射到数据库中。如果什么都不写,默认会映射到数据库中。

@Basic(fetch = FetchType.LAZY) 查询的时候当前字段延迟加载,避免浪费内存空间。


User类

package com.xulifei;

import javax.persistence.*;
import  java.util.Date;
/**
 * Created by john on 2017/7/3.
 */
@Entity //定义为映射实体
@Table(name = "user") //确定数据库表名
public class User {

     private Integer id;
    private String name;
    private Date birthday;
    private Gender gender = Gender.MAN;
    private String info;
    private Byte[] file;
    private String imagePath;

    public User() {
    }

    @Transient
    public String getImagePath() {
        return imagePath;
    }

    public void setImagePath(String imagePath) {
        this.imagePath = imagePath;
    }

    @Lob @Basic(fetch = FetchType.LAZY)
    public Byte[] getFile() {
        return file;
    }

    public void setFile(Byte[] file) {
        this.file = file;
    }

    @Lob
    public String getInfo() {
        return info;
    }

    public void setInfo(String info) {
        this.info = info;
    }
    @Enumerated(EnumType.STRING) @Column(length = 5,nullable = false)
    public Gender getGender() {
        return gender;
    }

    public void setGender(Gender gender) {
        this.gender = gender;
    }

    @Temporal(TemporalType.DATE)
    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public User(String name) {
        this.name = name;
    }

    //指定主键字段,并声明id策略
    @Id @GeneratedValue()
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Column(length = 10,nullable = false,name = "userName")
    public String getName() {
        return name;
    }


    public void setName(String name) {
        this.name = name;
    }
}

表结构:

 


测试类

@Test
public void save(){
       EntityManagerFactory factory = Persistence.createEntityManagerFactory("MYSQL");
       EntityManager em = factory.createEntityManager();
        em.getTransaction().begin();
       em.persist(new User("xinyanfang"));
        em.getTransaction().commit();
        em.close();
        factory.close();
   }


七、JPA查询更新、删除的API

1.JPA的查询API

find相对于hibernate的get方法,getReference相对于hibernate的load方法。
load得到的对象是其代理对象,只有当代理对象get数据时才会真正到数据库中查询,这就是懒加载的原理。所以和hibernate一样会出现懒加载异常。

因为当前JPA环境使用的是hibernate的实现,所以JPA的find、getReference等api下还是hibernate对session的操作。


2.查询更新

@Test
public void getUserById(){
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("xulifei");
    EntityManager em = factory.createEntityManager();

//         可以不要事务
    em.getTransaction().begin();

//查询出来的user放了一份在缓存中
    User user = em.find(User.class, 1);//hibernate get

 System.out.println(user.getName());
     user.setName("123");//更新查询出来的对象name属性
    em.getTransaction().commit();
    em.close();//缓存关闭
    factory.close();
}


     @Test
    public void getUserById2(){
         EntityManagerFactory factory = Persistence.createEntityManagerFactory("xulifei");
         EntityManager em = factory.createEntityManager();
//         可以不要事务
         em.getTransaction().begin();

//查询出来的user放了一份在缓存中
  User user = em.getReference(User.class, 1);//hibernate load
         System.out.println(user.getName());

User.setName(“123”)//更新查询出来的对象name属性
         em.getTransaction().commit();
         em.close();//缓存关闭
         factory.close();
     }

     @Test
    public void getUserById2(){
         EntityManagerFactory factory = Persistence.createEntityManagerFactory("xulifei");
         EntityManager em = factory.createEntityManager();
//         可以不要事务
         em.getTransaction().begin();

//查询出来的user放了一份在缓存中
         User user = em.getReference(User.class, 1);
         em.getTransaction().commit();
         em.close();//缓存关闭
         System.out.println(user.getName());//执行到这行代//码时出现懒加载异常
         factory.close();
     }

2.删除API

@Test
public void delete(){
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("xulifei");
    EntityManager em = factory.createEntityManager();
    em.getTransaction().begin();
    User user = em.find(User.class, 1);
    em.remove(user);
    em.getTransaction().commit();
    em.close();
    factory.close();
}


八、对象的四种状态以及转换


1.JPA对象的四种状态

新建态(new):新创建的实体对象,尚未存储在数据库中,没有在缓存中.

代码案例:


 


托管状态(managed):已经存储在数据库中和缓存中,并处于entitymanager对象的修改监控中(调用clear就不属于监控中).

代码案例:

 

游离态:已经存储在数据库中,但是不处于entitymanager对象的修改监控中(如使用了clear),可以使用merge方法将游离的对象修改同步。

代码案例:

 

使用merge方法并不能将游离的对象再次变为托管状态,因为不再受entitymanager对象的修改监控。

 


删除态:已经存储在数据库中,也已经在缓存中了,但已经被安排从数据库中删除.

代码案例:

 








2.四种状态之间的转换


 

方法解读

public void persist(Object entity)

persist方法可以将实例转换为managed(托管)状态。在调用flush()方法或提交事物后,实例将会被插入到数据库中。


对不同状态下的实例A,persist会产生以下操作:

1.如果A是一个new状态的实体,它将会转为managed状态;

2.如果A是一个managed状态的实体,它的状态不会发生任何改变。但是系统仍会在数据库执行INSERT操作;

3.如果A是一个removed(删除)状态的实体,它将会转换为managed状态;

4.如果A是一个detached(游离)状态的实体(如使用了托管状态下使用了clear方法),该方法会抛出IllegalArgumentException异常,具体异常根据不同的JPA实现有关。

public void merge(Object entity)

merge方法的主要作用是将用户对一个detached状态实体的修改进行归档,归档后将产生一个新的managed状态对象。


对不同状态下的实例A,merge会产生以下操作:

1.如果A是一个detached状态的实体,该方法会将A的修改提交到数据库,并返回一个新的managed状态的实例A2;

2.如果A是一个new状态的实体,该方法会产生一个根据A产生的managed状态实体A2;

3.如果A是一个managed状态的实体,它的状态不会发生任何改变。但是系统仍会在数据库执行UPDATE操作;

4.如果A是一个removed状态的实体,该方法会抛出IllegalArgumentException异常。

public void refresh(Object entity)

refresh方法可以保证当前的实例与数据库中的实例的内容一致。


对不同状态下的实例A,refresh会产生以下操作:

1.如果A是一个new状态的实例,不会发生任何操作,但有可能会抛出异常,具体情况根据不同JPA实现有关;

2.如果A是一个managed状态的实例,它的属性将会和数据库中的数据同步;

3.如果A是一个removed状态的实例,不会发生任何操作;

4.如果A是一个detached状态的实体,该方法将会抛出异常。

public void remove(Object entity)

remove方法可以将实体转换为removed状态,并且在调用flush()方法或提交事物后删除数据库中的数据。

对不同状态下的实例A,remove会产生以下操作:

1.如果A是一个new状态的实例,A的状态不会发生任何改变,但系统仍会在数据库中执行DELETE语句;

2.如果A是一个managed状态的实例,它的状态会转换为removed;

3.如果A是一个removed状态的实例,不会发生任何操作;

4.如果A是一个detached状态的实体,该方法将会抛出异常。







九、分析JPA与持久化实现产品对接的源代码

Persistence.java源码

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package javax.persistence;
public class Persistence {
    /** @deprecated */

//提供持久层具体实现的配置文件
    @Deprecated
    public static final String PERSISTENCE_PROVIDER = "javax.persistence.spi.PeristenceProvider";
    /** @deprecated */
    @Deprecated
    protected static final Set<PersistenceProvider> providers = new HashSet();
    private static PersistenceUtil util = new PersistenceUtil() {
        public boolean isLoaded(Object entity, String attributeName) {

//遍历所有的持久层实现
            List providers = Persistence.getProviders();
            Iterator i$ = providers.iterator();
            LoadState state;
            do {
                PersistenceProvider provider;
                if(!i$.hasNext()) {
                    i$ = providers.iterator();
                    do {
                        if(!i$.hasNext()) {
                            return true;
                        }
                        provider = (PersistenceProvider)i$.next();
                        state = provider.getProviderUtil().isLoadedWithReference(entity, attributeName);
                    } while(state == LoadState.UNKNOWN);

                    return state == LoadState.LOADED;
                }

                provider = (PersistenceProvider)i$.next();
                state = provider.getProviderUtil().isLoadedWithoutReference(entity, attributeName);
            } while(state == LoadState.UNKNOWN);
            return state == LoadState.LOADED;
        }
        public boolean isLoaded(Object object) {
            List providers = Persistence.getProviders();
            Iterator i$ = providers.iterator();
            LoadState state;
            do {
                if(!i$.hasNext()) {
                    return true;
                }
                PersistenceProvider provider = (PersistenceProvider)i$.next();
                state = provider.getProviderUtil().isLoaded(object);
            } while(state == LoadState.UNKNOWN);
            return state == LoadState.LOADED;
        }
    };
    public Persistence() {
    }
    public static EntityManagerFactory createEntityManagerFactory(String persistenceUnitName) {
        return createEntityManagerFactory(persistenceUnitName, (Map)null);
    }
    public static EntityManagerFactory createEntityManagerFactory(String persistenceUnitName, Map properties) {
        EntityManagerFactory emf = null;
        List providers = getProviders();
        Iterator i$ = providers.iterator();
        while(i$.hasNext()) {
            PersistenceProvider provider = (PersistenceProvider)i$.next();
            emf = provider.createEntityManagerFactory(persistenceUnitName, properties);
            if(emf != null) {
                break;
            }
        }
        if(emf == null) {
            throw new PersistenceException("No Persistence provider for EntityManager named " + persistenceUnitName);
        } else {
            return emf;
        }
    }

//根据配置文件获取所有的持久层实现
    private static List<PersistenceProvider> getProviders() {
        return PersistenceProviderResolverHolder.getPersistenceProviderResolver().getPersistenceProviders();
    }
    public static PersistenceUtil getPersistenceUtil() {
        return util;
    }
}


Javax.persistence.spi.PersistenceProvider,程序会在类路径地下寻找到这个文件,并读取这个配置文件里面指定的可持久化驱动。

 


内容为 org.hibernate.ejb.HibernatePersistence。Hibernate提供的可持久化驱动就是org.hibernate.ejb.HibernatePersistence这个类,这个类是Hibernate的 入口类,类似JDBC里面的驱动类。当然,不同的可持久化产品的入口类是不同的,调用JPA应用,它能使用Hibernate,是因为有这样一个驱动类,它起到了一个桥梁的作用,过渡到Hibernate 的产品上,这就是调用EntityManagerFactory factory = Persistence.createEntityManagerFactory("MYSQL"); 创建实体管理器方法的一些执行细节。

factory 是由Hibernate的可持久化驱动类创建出来的,如果观察Hibernate的实现类的话,会发现实际上 EntityManagerFactory 是对SessionFactory这个类进行了一层封装。 包括EntityManager类也是对Session对象进行了一层封装而已。 只要研究下Hibernate的JPA实现代码就可以观察出来。

 

十、使用JPQL语句进行删改查

1.查询

1.1.JPA也提供了hibernate一样的命名参数查询和位置参数查询,并且在对于位置参数还可以添加位置序号。

1.2.写面向对象的查询语言时,hibernate中可以省略select直接写from,但是如果换成别的持久层的面向对象语言可能就不一样了,所以一定要写成通用的,这也是JPA的规范。

1.3.查询sql例子

select u from User o where  u.id=:id

select  u from  User o where  u.id=?1

@Test
    public void query(){
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("MYSQL");

//命名参数查询语句

//String sql ="select u from User o where  u.id=:id";

//在JPA中的位置参数查询还可以添加位置序号

String sql ="select  u from  User o where  u.id=?1";
        EntityManager em = factory.createEntityManager();
         em.createQuery(sql);
         query.setParameter(1,1);

List<User> resultList = query.getResultList();

For(User u : resultList)
System.out.println(u.getName());
        em.close();
        factory.close();
    }

//单结果集查询

String sql ="select  count(u) from  User u where  u.id=?1";
        EntityManager em = factory.createEntityManager();
         em.createQuery(sql);
         query.setParameter(1,1);

Object obj = query.getSingleResult();
System.out.println(obj);
        em.close();
        factory.close();
    }



2.改

2.1.JPA也提供了hibernate一样的命名参数更改和位置参数更改,并且在对于位置参数还可以添加位置序号。

2.2.更新sql例子

update User o set o.name=:name where o.id=:id

update User o set o.name=:?1 where o.id=:?2

@Test
public void queryUpdate(){

    EntityManagerFactory factory = Persistence.createEntityManagerFactory("MYSQL");
    EntityManager em = factory.createEntityManager();
    em.getTransaction().begin();
    //Query query = em.createQuery("update User o set //o.name=:name where o.id=:id");
  Query query = em.createQuery("update User o set o.name=?1 where o.id=?2");  

query.setParameter("name","fei");
    query.setParameter("id",1);
    query.executeUpdate();
    em.getTransaction().commit();
    em.close();
    factory.close();

}

 

3.删

3.1.JPA也提供了hibernate一样的命名参数删除和位置参数删除,并且在对于位置参数还可以添加位置序号。

3.2.删除sql例子

@Test
    public void delete2(){
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("MYSQL");
        EntityManager em = factory.createEntityManager();
        em.getTransaction().begin();
//        Query query = em.createQuery("delete from User u //where u.id=:id");
        Query query = em.createQuery("delete from User u where u.id = ?1");
        query.setParameter(1,1);
        query.executeUpdate();
        em.getTransaction().commit();
        em.close();
        factory.close();
    }

注意:在hibernate中遇到关键字冲突需要我们自己加上反引号,但是在JPA中会自动加,但是我们最好还是不要写成关键字。

十一、JPA中的缓存、级联、targetEntity、Optional、FetchType、Orphan Removal、mappedBy

 

1.JPA的缓存

对于JPA2.0,缓存分为一级缓存和二级缓存(JPA1.0只支持一级缓存)。二级缓存通常是用来提高应用程序性能的,它可以避免访问以已经从数据库加载的数据,提高访问未被修改数据对象的速度。

JPA二级缓存是跨越持久化上下文的,是真正意义上的全局应用缓存。

持久化上下文就是JPA的一级缓存,通过在持久化上下文中存储持久化状态实体的快照,既可以进行脏检测,还可以当做持久化实体的缓存。一级缓存只在一次请求(entitymanager创建到关闭)范围内有效。

如果二级缓存激活(需要配置,默认关闭),JPA会先从一级缓存中寻找实体,未找到再从二级缓存中寻找。当二级缓存有效时,就不能依靠事务来保护并发的数据,而是依靠锁策略,如在确认修改后,需要手工处理乐观锁失败等。

注意:

I.本套教程均未开启二级缓存。

II.二级缓存只能缓存通过EntityManager的find或getReference查询到的实体,以及通过实体的getter方法获取到的关联实体;而不能缓存通过JPQL查询获得的数据。二级缓存通常用来提高性能,同时,使用二级缓存可能会导致提取到“陈旧”数据,也会出现并发写的问题。所以二级缓存最好是用在经常阅读数据,比较少更新数据的情况,而不应该对重要数据使用二级缓存。

二级缓存配置参考

http://blog.csdn.net/u010837612/article/details/47809415


III.find、getReference、persist、merge、refresh数据都会在一级缓存中备份。ceateQuery的语句都会向数据库发送操作请求,不会从一级缓存中拿。createQuery(update)不放置数据到一级缓存中也不会先更新一级缓存。Createquery(select)不会到缓存中查询,但是查出来的数据会放一份在一级缓存中。Delete肯定不会缓存。JPQL没有插入语句。


IV.默认情况下,EntityManager会检视所有的实体类对象的修改,在事务提交时,会自动将修改保存进数据库,相应的会消耗部分内存。可以用clear()解除对所有对象的监控。但clear()之后,对实体类所做的修改(如setName修改)将会丢失,不管你是在clear之前做的修改还是clear之后。但是一级缓存中原来的数据还是在的,只是修改不存在了。如果此时还想要同步对象的修改到数据库中并同步更新一级缓存数据,就使用merge(Object obj)方法。如果只想要获取修改后的对象并使一级缓存与数据库同步,而不是存在于原本缓存中的旧数据,就使用refresh方法,但必须处于托管状态(clear之后游离状态下使用会报错)且关闭事务,否则refresh报错或者无效。

代码示例

程序运行之前的表数据

 

@Test
    public void testRefresh(){
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("MYSQL");
        EntityManager em = factory.createEntityManager();
        em.getTransaction().begin();

//
        Query query1 = em.createQuery("select u from User u ");

//存入一级缓存
        List<User> resultList1 = query1.getResultList();       System.out.println(“resultList1:”+resultList1.get(0).getName());
        update();

//clear,所以修改没用,但不影响缓存中的数据
resultList1.get(0).setName("xinyanfang");
em.clear();//clear之后对象变为游离状态
//resultList1.get(0).setName("xulifei");

//想在clear之后同步修改就使用merge
//em.merge(resultList1.get(0));

//从一级缓存中取出旧数据bb,实际数据库cc
       User user2 = em.find(User.class,2);
 System.out.println(user2.getName());

        em.getTransaction().commit();
        em.close();
        factory.close();
    }

public void update(){
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("MYSQL");
    EntityManager em = factory.createEntityManager();
    em.getTransaction().begin();
    Query query = em.createQuery("update User u set u.name=?1 where u.id=?2");
   query.setParameter(1,"cc");
    query.setParameter(2,1);
    query.executeUpdate();
    em.getTransaction().commit();
    em.close();
    factory.close();
}

程序运行之后的表数据

 

控制台运行结果

resultList1:bb

resultList2:bb

提示:createquery方法把数据放入一级缓存,然后find方法缓存中取,但如果find在createquery之前,createquery不会从缓存中取即会到数据库查询。

V.在使用refresh时,不能使用事务即必须是查询,否则refresh无效。如果处于clear之后即游离的状态就会报错。

代码示例

  @Test
    public void testRefresh(){
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("MYSQL");
        EntityManager em = factory.createEntityManager();
        User user = em.find(User.class, 2);
        System.out.println(user.getName());

Update();
        em.refresh(user);//把数据库中实际数据拿到一级缓存中来。
        System.out.println(user.getName());
//        从一级缓存取出数据
     User user = em.find(User.class, 2);
        System.out.println(user.getName());
        em.close();
        factory.close();
    }

public void update(){
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("MYSQL");
    EntityManager em = factory.createEntityManager();
    em.getTransaction().begin();
    Query query = em.createQuery("update User u set u.name=?1 where u.id=?2");
   query.setParameter(1,"cc");
    query.setParameter(2,1);
    query.executeUpdate();
    em.getTransaction().commit();
    em.close();
    factory.close();
}


2.级联类型

CascadeType.REFRESH:级联刷新,也就是说,当你刚开始获取到了这条记录,那么在你处理业务过程 中,这条记录被另一个业务程序修改了(数据库这条记录被修改了),那么你获取的这条数据就不是最新的数 据,那你就要调用实体管理器里面的refresh方法来刷新实体,所谓刷新,大家一定要记住,它是获取数据,相当于执行select语句的,也就是重新获取数据。防止出现脏读,及时重新获取。

CascadeType.PERSIST:级联持久化,也就是级联保存。保存order的时候也保存orderItem,如果在数据 库里已经存在与需要保存的orderItem相同的id记录,则级联保存出错。

CascadeType.MERGE: 级联更新,也可以叫级联合并;当对象Order处于游离状态时,对对象Order里面 的属性作修改,也修改了Order里面的orderItems,当要更新对象Order时,是否也要把对orderItems的修改同 步到数据库呢?这就是由CascadeType.MERGE来决定的,如果设了这个值,那么Order处于游离状态时,会先 update order,然后for循环update orderItem,如果没设CascadeType.MERGE这个值,就不会出现for循环 update orderItem语句。

所以说,级联更新是控制对Order的更新是否会波及到orderItems对象。也就是说对Order进行update操作 的时候,orderItems是否也要做update操作呢?完全是由CascadeType.MERGE控制的。

CascadeType.REMOVE:当对Order进行删除操作的时候,是否也要对orderItems对象进行级联删除操作 呢?是的则写,不是的则不写。

如果在应用中,要同时使用这四项的话,可以改成cascade = CascadeType.ALL。

应用场合问题:这四种级联操作,并不是对所有的操作都起作用,只有当我们调用实体管理器的persist方法 的时候,CascadeType.PERSIST才会起作用;同样道理,只有当我们调用实体管理器的merge方法的时候, CascadeType.MERGE才会起作用,其他方法不起作用。 同样道理,只有当我们调用实体管理器的remove方

法的时候,CascadeType.REMOVE才会起作用。

注意: Query query = em.createQuery("delete from Person o where o.id=?1");这种JPQL操作是不会起作用的,因为配置里那四项都是针对实体管理器的对应的方法(persist、merge、remove、refresh)。

3.Optional

说明order这个是否是可选的,false表示必须的,true表示是可选的。在数据库中可以为null。


4.FetchType

 FetchType.LAZY  设置为延迟加载,当我们在数据库中取这条记录的时候,不会去取。

FetchType.EAGER  设置为及时加载

5.targetEntity

 如果目标实体使用的是泛型,的指明目标实体类型

@OneToMany(targetEntity = OrderItem.class,cascade = {CascadeType.REFRESH,CascadeType.PERSIST,CascadeType.MERGE,CascadeType.REMOVE},mappedBy = "order")
private Set<T> items = new HashSet<T>();


6.mappedBy

JPA规范:多的一方为维护方,一的一方为被维护方,那么我们需要使用mappedBy来声明。哪个类出现了mappedBy这个类就是被维护方。

mappedBy(被维护方在维护方中的引用),相当于hibernate的inverse=true,即当前方不能维护另一方,需要通过另一方维护当前方。

7.Orphan Removal

如果删除关系,是否删除关系上的实体,true表示会删除,false表示不删除。我们来举个例子说明一下,现在分别有User和Address两个实体,User类上有一个addresses的字段,表示引用多个Address实例,User和Address是一对多关系。大致的代码如下:

public class User{

    @OneToMany(orphanRemoval = true)

    private Set<Address> addresses;//...

}

public class Address{//...

}

我们操作User实体,并删除一个address实例。 

User user = em.find(User.class,1L);Address address = em.find(Address.class,1L);user.getAddresses().remove(address);em.save(user);

如果orphanRemoval = true,那么这个操作会删除address对象,如果为false,则只会删除他们的关系,将address对user的引用设置为null,不全部删除address对象。



十二、JPA中的一对多双向关联、级联操作、一对多延迟加载与关系维护

2.@OneToMany 一的一方使用

注解源码

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OneToMany {
    Class targetEntity() default void.class;
    CascadeType[] cascade() default {};

//默认多的一方延迟加载
    FetchType fetch() default FetchType.LAZY;
    String mappedBy() default "";
    boolean orphanRemoval() default false;
}


3.@ManyToOne 多的一方使用

注解源码

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ManyToOne {
    Class targetEntity() default void.class;
    CascadeType[] cascade() default {};

//默认一的一方立即加载
    FetchType fetch() default FetchType.EAGER;
    boolean optional() default true;
}

4.@JoinColumn 指明外键字段,多的一方使用

注解源码
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface JoinColumn {
    String name() default "";

    String referencedColumnName() default "";

    boolean unique() default false;

    boolean nullable() default true;

    boolean insertable() default true;

    boolean updatable() default true;

    String columnDefinition() default "";

    String table() default "";
}

5.一对多双向关联案例(实体必须有无参构造函数)

@Entity
@Table(name = "orders")
public class Order {
    private String orderId;
    private Float amount = 0f;
    private Set<OrderItem> items = new HashSet<OrderItem>();

/*

JPA支持的策略

TABLE,
SEQUENCE,
IDENTITY,
AUTO;

不支持字符类型的主键,所以得自己定义这么一列然后自己赋值。我们这边用的是去掉-的uuid,32字节长度

*/
    @Id
    @Column(length = 32)
    public String getOrderId() {
        return orderId;
    }

    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }

    @Column(nullable = false)
    public Float getAmount() {
        return amount;
    }

    public void setAmount(Float amount) {
        this.amount = amount;
    }

    @OneToMany(cascade = {CascadeType.REFRESH,CascadeType.PERSIST,CascadeType.MERGE,CascadeType.REMOVE},
            mappedBy = "order")
    public Set<OrderItem> getItems() {
        return items;
    }

    public void setItems(Set<OrderItem> items) {
        this.items = items;
    }
//    方便存取
    public void addOrderItem(OrderItem orderitem){
        orderitem.setOrder(this);
        this.items.add(orderitem);
    }
}


@Entity
@Table(name = "orderitem")
public class OrderItem {
//多的一方为关系维护端,关系维护端负责外键记录的更新,关系被维护端是没有权利更新外键记录
    private Integer id;
    private String productName;
    private Float sellPrice = 0f;
    private Order order;
    @Id @GeneratedValue
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Column(length = 40,nullable = false,name = "productName")
    public String getProductName() {
        return productName;
    }

    public void setProductName(String productName) {
        this.productName = productName;
    }

    @Column(nullable = false,name = "sellPrice")
    public Float getSellPrice() {
        return sellPrice;
    }

    public void setSellPrice(Float sellPrice) {
        this.sellPrice = sellPrice;
    }
//    optional默认为true
    @ManyToOne(cascade = {CascadeType.MERGE,CascadeType.REFRESH},optional = false)
//    指明外键名称
    @JoinColumn(name = "order_id")
    public Order getOrder() {
        return order;
    }

    public void setOrder(Order order) {
        this.order = order;
    }
}

测试类

  @Test
    public void test(){

         EntityManagerFactory factory = Persistence.createEntityManagerFactory("MYSQL");
         EntityManager em = factory.createEntityManager();
         em.getTransaction().begin();
         Order order = new Order();
         order.setAmount(34f);
         order.setOrderId(UUID.randomUUID().toString().replace("-",""));
         OrderItem orderItem1 = new OrderItem();
         orderItem1.setProductName("篮球");
         orderItem1.setSellPrice(90f);
//         orderItem1.setOrder(order);
         OrderItem orderItem2 = new OrderItem();
         orderItem2.setProductName("足球");
         orderItem2.setSellPrice(100f);
//         orderItem2.setOrder(order);
//         Set<OrderItem> set = new HashSet<>();
//         set.add(orderItem1);
//         set.add(orderItem2);
//          order.setItems(set);
         order.addOrderItem(orderItem1);
         order.addOrderItem(orderItem2);
         em.persist(order);
         em.getTransaction().commit();
         em.close();
         factory.close();
     }


 

十三、JPA中的一对一双向关联

 

OneToone源码

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OneToOne {
    Class targetEntity() default void.class;
    CascadeType[] cascade() default {};
    FetchType fetch() default FetchType.EAGER;
    boolean optional() default true;
    String mappedBy() default "";
    boolean orphanRemoval() default false;
}


 

@Entity

public class IDCard {

private Integer id;

private String cardNo;

private Person person;

public IDCard() {

}


public IDCard(String cardNo)

{ this.cardNo = cardNo;

}

@Id

@GeneratedValue

public Integer getId() {

return id;

}

public void setId(Integer id) { this.id = id;

}

@Column(length = 18,nullable=false)

public String getCardNo() {

return cardNo;

}

public void setCardNo(String cardNo) { this.cardNo = cardNo;

}

/*因為在Person里已经指定了idCard是必须要存在的,外键由person表维护,那么这里这个属性就是可选的, 外键由Person的option属性决定,就算你设置了这属性,其实它也不看你这属性。

 fetch:加载行为默认为立刻记载,凭one。*/

@OneToOne(mappedBy = "idCard", cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH } /*,optional = false*/)

public Person getPerson() { return person;

}



@Entity

public class Person {

private Integer id;

private String name;

private IDCard idCard;

public Person() {

}

@Id

@GeneratedValue

// 采用数据库Id自增长方式来生成主键值。

 public Integer getId() {

return id;

}

public void setId(Integer id) { this.id = id;

}

@Column(length = 10, nullable = false)

public String getName() {

return name;

}

public void setName(String name)

{

this.name = name;

}

@OneToOne(optional = false, cascade = CascadeType.ALL)

//指定关联字段在维护方表中的外键列名

@JoinColumn(name = "idCard_id")

public IDCard getIdCard() {

return idCard;

}

public void setIdCard(IDCard idCard)

{ this.idCard = idCard;

}

public Person(String name)

{ this.name = name;

}

}

注意:在一对一的情况下,哪一方作为维护方都可以。

测试类

@Test

public void save() {

EntityManagerFactory factory = Persistence

.createEntityManagerFactory("itcast"); EntityManager em = factory.createEntityManager(); em.getTransaction().begin();

Person person = new Person("老张"); // person是关系维护端。 person.setIdCard(new IDCard("1112222")); // 通过person把idCard放进去。不用往idcard放person。

 em.persist(person); // 先保存idCard,得到保存记录的id,用id作为外键的值,再保存person

em.getTransaction().commit();

em.close();

factory.close();

}

}


谁是关系维护端,谁就负责外键字段的更新。 Person是关系维护端,IDCard是关系被维护端,怎么维护更新呢?往Person里面设置idCard,这样就相当于

把关系建立起来了;如果通过IDCard设置person的话,那么这种关系是建立不起来的,因为IDCard是关系被维护端。




十四、JPA中的多对多双向关联实体定义与注解设置

 

@Entity

public class Student {

private Integer id;

private String name;

private Set<Teacher> teachers = new HashSet<Teacher>();


@Id @GeneratedValue //id作为实体标识符,并且采用数据库id自增长的方式生成主键值。

 public Integer getId() {

return id;

}


public void setId(Integer id) { this.id = id;

}


@Column(length = 10, nullable = false)

public String getName() {

return name;


}


public void setName(String name) { this.name = name;

}


@ManyToMany(cascade = CascadeType.REFRESH)

@JoinTable(name = "student_teacher", inverseJoinColumns = @JoinColumn(name="teacher_id"),JoinColumns = @JoinColumn(name = "student_id")

public Set<Teacher> getTeachers() {

return teachers;


}

/*

inverseJoinColumns:被维护端在中间表中的外键的定义。

@JoinColumn:维护端在中间表中的外键定义。

如果自己不定义这两个,那么中间表里面的字段默认由JPA帮我们自动生成。

*/


public void setTeachers(Set<Teacher> teachers) { this.teachers = teachers;

}

}


Teacher.java

@Entity

public class Teacher {

private Integer id; private String name;

private Set<Student> students=new HashSet<Student>();

@Id @GeneratedValue //id作为实体标识符,并且采用数据库id自增长的方式生成主键值。

 public Integer getId() {

return id;

}

public void setId(Integer id) { this.id = id;

}

@Column(length = 10, nullable = false)

public String getName() {

return name;

}

public void setName(String name)

{ this.name = name;

}

@ManyToMany(cascade=CascadeType.REFRESH,mappedBy="teachers")

//mappedBy="teachers",表示关系由Student对象维护。"teachers"与Student对象中的teachers属性对应 。

public Set<Student> getStudents() {

return students;

}

/*

cascade:

CascadeType.PERSIST:级联保存不要,学生没来之前,老师就已经在了。

CascadeType.MERGE:级联更新不要,把学生的信息改了,没必要修改相应的老师的信息

CascadeType.REMOVE:级联删除更不要,如果双方都设了级联删除,假如删除学生,会删除相应老师,又会删除相应的学生。

这里只需设置级联刷新CascadeType.REFRESH就可以了,事实上refresh方法也很少使用。

mappedBy: 通过这个属性来说明老师是关系被维护端。

fetch: 加载行为默认是延迟加载(懒加载),因为是Many方。 这里不需要设置。

*/

public void setStudents(Set<Student> students) { this.students = students;

}

}


测试类

public class ManyToManyTest {

@BeforeClass

public static void setUpBeforeClass() throws Exception {

}

@Test

public void save() {

EntityManagerFactory factory = Persistence.createEntityManagerFactory("itcast") factory.close();

}

}

双向多对多关系是一种对等关系,既然是对等关系,也就是说我们可以人为决定谁是关系维护端,谁是关系 被维护端,这里选学生做关系维护端。那么以后就只能通过学生来维护老师跟学生的关系。

假设:

老师A id是1 学生B id是1

那通过什么东西把他们的关系确立起来呢?采用什么来存放他们的关联关系呢?是中间表(关联表)。

学生A和老师B建立起关系,首先要找到关系维护端,是学生,就要通过学生这个关系维护端,学生 A.getTeachers().add(Teacher);这样就能把老师跟学生的关系确立起来了。确立起来后,反应在中间表里面 就是insert into...一条语句

如果学生A要把老师B开掉,那就要解除关系,也是通过关系维护端学生A,反映在面向对象的操作就是 学 生A.getTeachers().remove(Teacher);执行这句代码的时候,在底层JDBC它会对中间表做delete from...语句的 操作。

我们都是通过关系维护端来进行操作的,以后在双向关系中一定要找准谁是关系维护端,谁是关系被维护端

 

 

十五、JPA中的多对多双向关联的各项关系操作

 

@Entity

public class Student {

private Integer id;

private String name;

private Set<Teacher> teachers = new HashSet<Teacher>();

public Student() {

}

public Student(String name) { this.name = name;

}

@Id

@GeneratedValue

// id作为实体标识符,并且采用数据库的id自增长方式生成主键值。

public Integer getId() {

return id;


}

public void setId(Integer id) { this.id = id;

}

@Column(length = 10, nullable = false)

public String getName() {

return name;

}

public void setName(String name) { this.name = name;

}

@ManyToMany(cascade = CascadeType.REFRESH)

@JoinTable(name = "student_teacher", inverseJoinColumns = @JoinColumn(name = "teacher_id"),@JoinColumn=( name = "student_id"))

public Set<Teacher> getTeachers() {

return teachers;

}


/*

inverseJoinColumns:被维护端在中间表中的外键的定义。

@JoinColumn:维护端在中间表中的外键定义。

如果自己不定义这两个,那么中间表里面的字段默认由JPA帮我们自动生成。

*/

public void setTeachers(Set<Teacher> teachers) { this.teachers = teachers;

}

Public void addTeacher(Teacher teacher) { this.teachers.add(teacher);

}

public void removeTeacher(Teacher teacher) {

if(this.teachers.contains(teacher)){

this.teachers.remove(teacher);

}

}

}


Teacher.java

@Entity

public class Teacher {

private Integer id; private String name;

private Set<Student> students = new HashSet<Student>();


public Teacher() {

}


public Teacher(String name) { this.name = name;

}

@Id

@GeneratedValue

// id作为实体标识符,并且采用数据库的id自增长方式生成主键值。

public Integer getId() {

return id;

}

public void setId(Integer id) { this.id = id;

}


@Column(length = 10, nullable = false) public String getName() {

return name;


}


public void setName(String name)

{ this.name = name;

}


@ManyToMany(cascade = CascadeType.REFRESH, mappedBy = "teachers")

public Set<Student> getStudents() {

return students;


}


public void setStudents(Set<Student> students) { this.students = students;

}

//重写hashCode和equals方法,按id比较

@Override

public int hashCode() {

final int prime = 31; int result = 1;

result = prime * result + ((id == null) ? 0 : id.hashCode());

//判断的依据是,如果id不为null的话,就返回id的哈希码。 return result;

}


@Override

public boolean equals(Object obj) { if (this == obj)

return true; if (obj == null)

return false;

if (getClass() != obj.getClass()) return false;

final Teacher other = (Teacher) obj; if (id == null) {

if (other.id != null)

return false;

} else if (!id.equals(other.id)) return false;

return true;

}

}


测试类

public class ManyToManyTest {


@BeforeClass

public static void setUpBeforeClass() throws Exception {

}


@Test

public void save() {

EntityManagerFactory factory = Persistence

.createEntityManagerFactory("MYSQL");

EntityManager em = factory.createEntityManager(); em.getTransaction().begin();

//虽然保存了两方,但是由于关系没有建立,所以中间表没有数据

em.persist(new Student("小张同学"));

em.persist(new Teacher("李勇老师"));

em.getTransaction().commit();

em.close();

factory.close();

}


/*

* 建立学生跟老师的关系

*/

@Test

public void buildTS() {

EntityManagerFactory factory = Persistence

.createEntityManagerFactory("itcast");

EntityManager em = factory.createEntityManager(); em.getTransaction().begin();

Student student = em.find(Student.class, 1);

 // 首先要得到学生,因为学生是关系的维护方

student.addTeacher(em.getReference(Teacher.class, 1));

//所谓建立跟老师的关系,无非就是把老师加进集合里面去,修改了提交之后就会持久化到数据库中。

//建立关系,体现在JDBC上面,就是添加一条记录进中间表。

em.getTransaction().commit(); em.close();

factory.close();

}



/*

* 解除学生跟老师的关系

*/

@Test

public void deleteTS() {

EntityManagerFactory factory = Persistence

.createEntityManagerFactory("itcast");

EntityManager em = factory.createEntityManager(); em.getTransaction().begin();

Student student = em.find(Student.class, 1);

// 首先要得到学生,因为学生是关系维护方,然后将老师从学生中移除

 student.removeTeacher(em.getReference(Teacher.class, 1));

//所谓解除跟老师的关系,无非就是把老师从集合里面删去,修改提交之后就会持久化到数据库中。

//解除关系,体现在JDBC上面,就是在中间表删除一条记录。

em.getTransaction().commit();

em.close();

factory.close();

}



/*

* 删除老师,老师已经跟学生建立起了关系(错误写法)

*/

@Test

public void deleteTeacher1() {

EntityManagerFactory factory = Persistence

.createEntityManagerFactory("itcast");

EntityManager em = factory.createEntityManager(); em.getTransaction().begin();

em.remove(em.getReference(Teacher.class, 1));

//并不需要发生数据装载行为,只需要一个托管状态的实体,所以用getReference可以提供性能

em.getTransaction().commit(); em.close();

factory.close();

}

/*

该方法会出错,因为删除老师就要删除中间表外键约束,但被维护端没有权利更新外键。

会抛出以下错误: Caused by: java.sql.BatchUpdateException:

Cannot delete or update a parent row: a foreign key constraint fails (`itcast/student_teacher`, CONSTRAINT `FKD4E389DE1D49449D` FOREIGN KEY (`teacher_ REFERENCES `teacher` (`id`))

关系被维护端没有权力更新外键,所以不会删除中间表的记录。

*/


/*

* 删除老师,老师已经跟学生建立起了关系(正确写法)

*/


@Test

public void deleteTeacher2() {

EntityManagerFactory factory = Persistence

.createEntityManagerFactory("itcast");

EntityManager em = factory.createEntityManager();

em.getTransaction().begin();

Student student = em.find(Student.class, 1);

Teacher teacher = em.getReference(Teacher.class, 1);

//并不需要发生数据装载行为,只需要一个托管状态的实体,所以用getReference可以提供性能

student.removeTeacher(teacher);

//student是关系维护端,有权利删除外键,只要在对象中删除了

//teacher,那么中间表中相关外键就没有了

//想要删除teacher记录,必须先通过student解除关系才行。 em.remove(teacher);

em.getTransaction().commit(); em.close();

factory.close();

}

/*

* 删除学生,老师已经跟学生建立起了关系

*/

@Test

public void deleteStudent() {

EntityManagerFactory factory = Persistence

.createEntityManagerFactory("itcast");

EntityManager em = factory.createEntityManager(); em.getTransaction().begin();

Student student = em.getReference(Student.class, 1);

em.remove(student); //这样是可以删除学生的,尽管目前是有关系,中间表有关联记录,因为多的一方是boolean orphanRemoval() default false;默认值,所以只会删除中间表记录,不会删除被维护方实体。


em.getTransaction().commit();

 em.close();

factory.close();

}

}


 

十六、JPA详解_JPA中的联合主键

两个或多个字段组成的主键,我们叫联合主键。在面向对象中,我们用JPA怎么定义这种情况呢? 怎么定义联合主键?用面向对象的思想来思考的话,联合主键里的复合主键(字段),可以把它看成一个整

体,然后采用一个主键类来描述这个复合主键的字段。

关于联合主键类,大家一定要遵守以下几点JPA规范:

1. 必须提供一个public的无参数构造函数。

2. 必须实现序列化接口。

3. 必须重写hashCode()和equals()这两个方法。这两个方法应该采用复合主键的字段作为判断这个对象是 否相等的。

4. 联合主键类的类名结尾一般要加上PK两个字母代表一个主键类,不是要求而是一种命名风格。


ArtLinePK.java

//embeddable把联合主键类的属性归到实体类对象的表中,并在表中给予这两个数据以主键约束。

@Embeddable

//这个注解代表ArtLinePK这个类是用在实体里面,告诉JPA的实现产品:在实体类里面只是使用这个类定义的属性。

//简单的理解为:ArtLinePK里的属性可以看成是ArtLine类里的属性,好比ArtLinePK的属性就是在ArtLine里定义

public class ArtLinePK implements Serializable{

private String startCity;

private String endCity;

public ArtLinePK() {

}

public ArtLinePK(String startCity, String endCity) { this.startCity = startCity;

this.endCity = endCity;

}


@Column(length=3)

public String getStartCity() { return startCity;

}


public void setStartCity(String startCity) { this.startCity = startCity;

}


@Column(length=3)

public String getEndCity() { return endCity;

}


public void setEndCity(String endCity) { this.endCity = endCity;

}


@Override

public int hashCode() {

final int prime = 31;

int result = 1;

result = prime * result + ((endCity == null) ? 0 : endCity.hashCode());

result = prime * result+ ((startCity == null) ? 0 : startCity.hashCode());

return result;

}



@Override

public boolean equals(Object obj) { if (this == obj)

return true;

if (obj == null)

return false;

if (getClass() != obj.getClass()) return false;

final ArtLinePK other = (ArtLinePK) obj;

 if (endCity == null) {

if (other.endCity != null)

return false;

} else if (!endCity.equals(other.endCity)) return false;

if (startCity == null) {

if (other.startCity != null) return false;

} else if (!startCity.equals(other.startCity)) return false;

return true;

}


}


这个联合主键类,应该作为实体类的一个主键(实体标识符,id)。 ArtLine.java


@Entity

public class ArtLine {

private ArtLinePK id; //用面向对象的思想去思考的话,这个复合主键看成一个整体。

private String name;


public ArtLine() {

}


public ArtLine(ArtLinePK id) { this.id = id;

}


public ArtLine(String startCity, String endCity, String name) { this.id = new ArtLinePK(startCity, endCity);

this.name = name;


}


@EmbeddedId //按照JPA规范要求,我们并不是用@Id来标注它。

//@EmbeddedId 这个注解用于标注id这个属性为实体的标识符。

public ArtLinePK getId() {

return id;

}


public void setId(ArtLinePK id) { this.id = id;

}


@Column(length=20)

public String getName() {

return name;


}


public void setName(String name) { this.name = name;

}

}

我们的复合主键类,目前为止,已经定义好了。定义好了之后,接着我们就看它生成的数据库表是否满足复合

主键这么一种情况。

ArtLineTest.java package junit.test;


public class ArtLineTest {

@BeforeClass

public static void setUpBeforeClass() throws Exception {

}

@Test public void save(){

EntityManagerFactory factory = Persistence

.createEntityManagerFactory("itcast");

EntityManager em = factory.createEntityManager();

 em.getTransaction().begin();

em.persist(new ArtLine("PEK","SHA","北京飞上海"));

em.getTransaction().commit(); em.close();

factory.close();

}

}

 

十七、继承映射

 在JPA中,实体继承关系的映射策略共有三种:单表继承策略(table per class)、Joined策略(table per subclass)和Table_PER_Class策略。


1.单表继承策略(SINGLE_TABLE)

   单表继承策略,父类实体和子类实体共用一张数据库表,在表中通过一列辨别字段来区别不同类别的实体。表名由父类命令。具体做法如下:


a.在父类实体的@Entity注解下添加如下的注解:

@Inheritance(Strategy=InheritanceType.SINGLE_TABLE)

@DiscriminatorColumn(name=”辨别字段列名”)

@DiscriminatorValue(指定实体辨别字段的列值)


b.在子类实体的@Entity注解下添加如下的注解:

@DiscriminatorValue(指定实体辨别字段的列值)  


Employee父类

 @Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type_name")
@DiscriminatorValue("Emp")
public class Employee {

    @Id
    @GeneratedValue
    private Integer id;
    private String name;



    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }



Sales子类

@Entity
@DiscriminatorValue("sal")
public class Sales extends Employee {


    @Column(name = "saleThing",length = 20)
     private String saleThing;

    public String getSaleThing() {
        return saleThing;
    }

    public void setSaleThing(String saleThing) {
        this.saleThing = saleThing;
    }

}

Skiller子类

@Entity
@DiscriminatorValue("ski")
public class Skiller  extends  Employee {


    @Column(name = "skill",length = 20)
    private String skill;

    public String getSkill() {
        return skill;
    }

    public void setSkill(String skill) {
        this.skill = skill;
    }
}


 以上通过列DISCRIMINATORVALUE的不同,区分具体父子实体。


实际表结构如下:

 


当你使用Employee实体时,实际表的type_name字段为emp,

skill与saleThing永远是空的。

当使用Sales实体时,实际表的type_name字段为sal,skill永远是空,saleThing为实际值。

Skiller同理.

测试类

 @Test
public void addDepart() {
    EntityManager em = JpaUtil.getEntityManager();
    EntityTransaction tx = em.getTransaction();
    tx.begin();
    Sales sales = new Sales();
    sales.setName("xulifei");
    sales.setSaleThing("huoqiu");
    em.persist(sales);

    tx.commit();
    em.close();
}

结果

 




2.Joined策略


    父类实体和子类实体分别对应数据库中不同的表,子类实体的表中只存在其扩展的特殊属性,父类的公共属性保存在父类实体表中,表名各自命令。具体做法:


@Inheritance(Strategy=InheritanceType.JOINED)

//定义子类表的主键列名,同时也是外键,引用父类ID

@PrimaryKeyJoinColumn(子类表的主键列名)

子类实体不需要特殊说明。


 Employee父类

@Entity
@Table(name = "T_EMPLOYEE")
@Inheritance(strategy = InheritanceType.JOINED)
public class Employee {
    @Id
    @GeneratedValue
    @Column(name = "ID")
    private Integer id;
    @Column(name = "NAME",length = 10)
    private String name;
/*    @ManyToOne(cascade = CascadeType.REFRESH)
    private Department depart;*/
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }


Sales子类

@Entity
@Table(name = "T_SALES")
@PrimaryKeyJoinColumn(name = "SALES_ID")
public class Sales extends Employee {

    @Column(name = "SALETHING",length = 20)
     private String saleThing;

    public String getSaleThing() {
        return saleThing;
    }
    public void setSaleThing(String saleThing) {
        this.saleThing = saleThing;
    }
}

Skiller子类

@Entity
@Table(name = "T_SKILLER")
@PrimaryKeyJoinColumn(name = "SKILLER_ID")
public class Skiller  extends Employee {


    @Column(name = "SKILL",length = 20)
    private String skill;

    public String getSkill() {
        return skill;
    }

    public void setSkill(String skill) {
        this.skill = skill;
    }
}



实际表结构如下:

 生成三张表

T_employee父表

 

T_sales子表

 

T_skiller子表

 


测试类

@Test
public void addDepart() {
    EntityManager em = JpaUtil.getEntityManager();
    EntityTransaction tx = em.getTransaction();
    tx.begin();
    Sales sales = new Sales();
    sales.setName("xulifei");
    sales.setSaleThing("huoqiu");
    em.persist(sales);
    Skiller skiller = new Skiller();
    skiller.setName("xinyanfang");
    skiller.setSkill("daqiu");
   em.persist(skiller);
    tx.commit();
    em.close();


}

结果

 



3.Table_PER_Class策略:


Table_PER_Class策略,父类和子类实体每个类分别对应一张数据库中的表,子类表中保存所有属性,包括从父类实体中继承的属性。 一旦使用这种策略就意味着你不能使用AUTO generator 和IDENTITY generator,即主键值不能采用数据库自动生成。

具体做法:

只需在父类实体的@Entity注解下添加如下注解:

@Inheritance(Strategy=InheritanceType.TABLE_PER_CLASS)


Employee
@Entity
@Table(name = "T_EMPLOYEE")
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Employee {

    @Id
    @Column(name = "ID",length = 32)
    private String id;
    @Column(name = "NAME", length = 10)
    private String name;


    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}


Sales

@Entity
@Table(name = "T_SALES")
public class Sales extends Employee {


    @Column(name = "SALETHING",length = 20)
     private String saleThing;

    public String getSaleThing() {
        return saleThing;
    }

    public void setSaleThing(String saleThing) {
        this.saleThing = saleThing;
    }

}

Skiller

@Entity
@Table(name = "T_SKILLER")
public class Skiller  extends Employee {


    @Column(name = "SKILL",length = 20)
    private String skill;

    public String getSkill() {
        return skill;
    }

    public void setSkill(String skill) {
        this.skill = skill;
    }
}



实际表结构如下:

T_employee

 

T_sales

 

T_skiller

 

测试同理


映射注意

1.JPA规范中关联字段列名以及中间表都是在维护方设置的

原创粉丝点击