您的位置:首页 > 其它

第11章-使用对象-关系映射持久化数据

2017-07-27 10:02 387 查看
Spring与Java持久化API

简介

Java持久化API(Java Persistence API,
JPA);

基于POJO的持久化机制;

Spring中配置JPA

Spring中使用JPA的第一步是要在Spring应用上下文中将实体管理器工厂(entity
manager factory)按照bean的形式来进行配置;

 

1、配置实体管理器工厂

基于JPA的应用程序需要使用EntityManagerFactory的实现类来获取EntityManager实例。

JPA定义了两种类型的实体管理器:

应用程序管理类型(Application-managed):当应用程序向实体管理器工厂直接请求实体管理器时,工厂会创建一个实体管理器。在这种模式下,程序要负责打开或关闭实体管理器并在事务中对其进行控制器。这种方式的实体管理器适合于不运行在Java EE容器中的独立应用程序。

容器管理类型(Container-managed):实体管理器由Java EE创建和管理。应用程序根本不与实体管理器工厂打交道。相反,实体管理器直接通过注入或J
4000
NDI来获取。容器负责配置实体管理器工厂。这种类型的实体管理器最适用于Java
EE容器,在这种情况下会希望在persistence.xml指定的JPA配置之外保持一些自己对JPA的控制。

以上的两种实体管理器实现了同一个EntityManager接口。关键区别在于:EntityManager的创建和管理方式。

应用程序管理类型的EntityManager是由EntityManagerFactory创建;

容器管理类型的EntitymanagerFactory是通过PersistenceProvider的createContainerEntityManagerFactory()方法获得;

两种类型Spring都会负责管理EntityManager。应用程序场景Spring承担了应用程序的角色并以透明的方式处理EntityManag;容器管理场景Spring会担当容器的角色。

两种实体管理器工厂分别对应Spring工厂Bean:

LocalEntityManagerFactoryBean生成应用程序管理类型的EntityManagerFactory;

LocalContainerEntityManagerFactoryBean生成容器管理类型的EntityManagerFactory;

<1>配置应用程序管理类型的JPA

对于应用程序管理类型的实体管理器工厂来说,他绝大部分配置信息来源于一个名为persistence.xml的配置文件。这个文件必须位于类路径下的META-INF目录下。

persistence.xml的作用在于定义一个或多个持久化单元。持久化单元是同一个数据源下的一个或多个持久化类。persistence.xml列出了一个或多个的持久化类以及一些其他的配置如数据源和基于XML的配置文件。

 

<?xml version="1.0" encoding="UTF-8"?>

 <persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0" >

    <!--必须要有name属性,不能为空
-->

     <persistence-unit name="spitterPU">

          <class>com.habuma.spittr.domain.Spitter</class>

          <class>com.habuma.spittr.domain.Spittle</class>

          <!--厂商的特定属性 -->

          <properties>

             <!--配置Hibernate方言
-->

             <property  name="hibernate.dialect"

value="org.hibernate.dialect.MySQL5Dialect" />

             <!--配置数据库驱动 -->

             <property   name="hibernate.connection.driver_class"

value="com.mysql.jdbc.Driver" />

             <!--配置数据库用户名 -->

             <property  name="hibernate.connection.username" value="root" />

             <!--配置数据库密码 -->

             <property  name="hibernate.connection.password" value="root" />

             <!--配置数据库url -->

             <property name="hibernate.connection.url"

value="jdbc:mysql://localhost:3306/jpa?useUnicode=true&characterEncoding=UTF-8" />

             <!--设置外连接抓取树的最大深度 -->

             <property name="hibernate.max_fetch_depth" value="3" />

             <!--自动输出schema创建DDL语句
-->

             <property name="hibernate.hbm2ddl.auto" value="update" />    

          </properties>

     </persistence-unit>

 </persistence>

 

对应到Spring中的配置:

@Bean

public LocalEntityManagerFactoryBean entityManagerFactoryBean() {

LocalEntityManagerFactoryBean emfb

= new LocalEntityManagerFactoryBean();

emfb.setPersistenceUnitName(“spitterPU”);

return emfb;

}

其中赋给persistenceUnitName属性的值就是persistence.xml中持久化单元的名称。

总结:完全由应用程序本身来负责获取EntityManagerFactory,这是通过JPA实现的PersistenceProvider做到的。

 

<2>使用容器管理类型的JPA

容器管理的JPA采用了一个不同的方式。当运行在容器中时,可以使用容器提供的信息来生成EntityManagerFactory。

你可以将数据源信息配置在Spring应用上下文中,而不是persistence.xml中

 

在Spring中配置容器管理类型的JPA代码如下:

@Bean

public LocalContainerEntityManagerFactoryBean entityManagerFactory(

DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) {

LocalContainerEntityManagerFactoryBean emfb =

new LocalContainerEntityManagerFactoryBean();

emfb.setDataSource(dataSource);

emfb.setJpaVendorAdapter(jpaVendorAdapter);

emfb.setPackagesToScan(“com.habuma.spittr.domain”);

return emfb;

}

jpaVendorAdapter属性用于指明所使用的是那个厂商的JPA实现:

EclipseLinkJpaVendorAdapter

HibernateJpaVendorAdapter

OpenJpaVendorAdapter

TopLinkJpaVendorAdapter(在Spring 3.1版本中,已经将其废弃了)

@Bean

public JpaVendorAdapter jpaVendorAdapter() {

HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter ();

adapter.setDatabase(“HSQL”);  //最重要的是database属性

adapter.setShowSql(true);

adapter.setGenerateDdl(false);

adapter.setDatabasePlatform(“org.hibernate.dialect.HSQLDialect”);

return adapter;

}

setPackagesToScan属性会让LocalContainerEntityManagerFactoryBean扫描包,查找带有@Entity注解的类。

 

从JNDI获取实体管理器工厂

如果将Spring应用程序部署在应用服务器中,EntityManagerFactory可能已经创建好了并且位于JNDI中等待查询使用。

<jee:jndi-lookup id=”emf” jndi-name=”persistence/spitterPU”>

或者

@Bean

public JndiObjectFactoryBean entityManagerFactory() {

JndiObjectFactoryBean jndiObjectFB = new JndiObjectFactoryBean();

jndiObjectFB .setJndiName(“jdbc/spittrDB”);

return jndiObjectFB;

}

JndiObjectFactoryBean是FactoryBean接口的实现,他能够创建EntityManagerFactory。

2、编写基于JPA的Repository

方法1:

@Repository

@Transactional

public class JpaSpitterRepository implements SpitterRepository {

 

@PersistenceUnit

private EntityManagerFactory emf;

 

public void addSpitter(Spitter spitter) {

emf.createEntityManager().persist(spitter);

}

public Spitter getSpitterById(long id) {

return emf.createEntityManager().find(Spitter.class, id);

}

public void saveSpitter(Spitter spitter) {

emf.createEntityManager().merge(spitter);

}

}

@PersistenceUnit注解,Spring会将EntityManagerFactory注入到Repository中

EntityManager并不是线程安全的,一般来说并不适合注入到像Repository这样共享的单例Bean中。

 

方法2:

@Repository

@Transactional

public class JpaSpitterRepository implements SpitterRepository {

 

@PersistenceContext

private EntityManager em;

 

public void addSpitter(Spitter spitter) {

em.persist(spitter);

}

public Spitter getSpitterById(long id) {

return em.find(Spitter.class, id);

}

public void saveSpitter(Spitter spitter) {

em.merge(spitter);

}

}

@PersistenceContext并不会真正注入EntityManager;他没有将真正的EntityManager设置给Repository,而是给了他一个EntityManager的代理。真正的EntityManager是与当前事务相关联的那一个,如果不存在这样的EntityManager的话,就会创建一个新的。这样的话,我们就能始终以线程安全的方式使用实体管理器。

还需要了解@PersistenceUnit和@PersistenceContext并不是Spring的注解,它们是由JPA规范提供的。为了让Spring理解这些注解,并注入EntityManager
Factory或EntityManager,我们必须要配置Spring的PersistenceAnnotationBeanPostProcessor。如果你已经使用了<context:annotation-config>或<context:component-scan>,那么你就不必再担心了,因为这些配置元素会自动注册PersistenceAnnotationBeanPostProcessor
bean。否则的话,我们需要显

式地注册这个bean:

@Bean

public PersistenceAnnotationBeanPostProcessor paPostProcessor() {

    return new PersistenceAnnotationBeanPostProcessor();

}

上述两种方法使用了@Repository和@Transactional注解。@Transactional表明这个Repository中的持久化方法是在事务上下文中执行的。

对于@Repository注解,我们需要为Repository添加@Repository注解,这样PersistenceExceptionTranslationPostProcessor就会知道要将这个bean产生的异常转换成Spring的统一数据访问异常。

3、借助Spring Data实现自动化的JPA Repository

借助Spring Data,以接口定义的方式创建Repository

public interface SpitterRepository extends JpaRepository<Spitter, Long> {}

XML配置自动化JPA Repository

<?xml version=”1.0” encoding=”UTF-8”>

<beans xmlns=”http://www.springframework.org/schema/beans”

xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”

xmlns:jpa=”http://www.springframework.org/schema/data/jpa

xsi:schemaLocation=”http://www.springframework.org/scehma/data/jpa/spring-jpa-1.0.xsd”>

<jpa:repositories base-package=”com.habuma.spittr.db” />

</beans>

Java配置自动化JPA Repository

@Configuration

@EnableJpaRepositories(basePackages=”com.habuma.spittr.db”)

public class JpaConfiguration() {

}

<1>定义查询方法

<2>声明自定义查询

通过@Query注解实现

<3>混合自定义的功能

public interface SpitterSweeper {

    int eliteSweep();

}

public interface SpitterRepository extends JpaRepository<Spitter,
Long>,
SpitterSweeper {

    。。。。。

}

public class SpitterRepositoryImpl implements SpitterSweeper{

@PersistenceContext

private EntityManager em;

 

public int eliteSweep() {

}

}

其中SpitterRepositoryImpl的后缀Impl是默认情况下,也可以修改默认的后缀,秩序在配置

@EnableJpaRepositories时候,设置repositoryImplementationPostfix属性即可

@EnableJpaRepositories(basePackages=””, repositoryImplementationPostfix=”Helper”)

XML的配置方式

<jpa:repositories base-package=””

repository-impl-postfix=”Helper” />

 

 

 

 

 

 

 

JPA技术杂记

 

@MappedSuperclass注解

基于代码复用和模型分离的思想,在项目开发中使用JPA的@MappedSuperclass注解将实体类的多个属性分别封装到不同的非实体类中。

1、@MappedSuperclass注解只能标注在类上;

2、标注为@MappedSuperclass的类将不是一个完整的实体类,他讲不会映射到数据库表,但是他的属性都将映射到其子类的数据库字段中。

3、标注为@MappedSuperclass的类不能再标注@Entity或@Table注解,也无需实现序列化接口。但是如果一个标注为@MappedSuperclass的类继承了另外一个实体类或者另外一个同样标注了@MappedSuperclass的类的话,他将可以使用@AttributeOverride或@AttributeOverrides注解重定义其父类(无论是否是实体类)的属性映射到数据库表中的字段。比如可以重定义字段名或长度等属性,使用@AttributeOverride中的子属性@Column进行具体的定义。

4、此外,这样的类还可以直接标注@EntityListeners实体监听器,他的作用范围仅在其所有继承类中,并且实体监听器同样可以被其子类继承或重载。

5、由于标注为@MappedSuperclass的类将不是一个完整的实体类,因此其不能标注@Table。

8.比较实体继承与实体映射的区别
实体继承的三种策略分别是:SINGLE_TABLE(所有继承的实体都保存在同一张数据库表中),JOINED(每个实体子类都将保存在一个单独的表中),TABLE_PER_CLASS(有继承关系的所有实体类都将保存在单独的表中)。
实体映射最类似于JOINED实体继承方式,他也是将实体子类单独保存为一张表,但是两者最大的区别就在于:查询的时候JOINED使用的是多态查询,在查询父类时其所有实体子类的数据也将同时被查询出,因此查询时间和性能都将有影响。但是实体映射方式的数据库查询等同于没有实体继承关系的查询,也就是说,他仅在实体层体现出一种继承的关系却并没有在数据库中体现这样一种关系,他的操作都是独立的并且将不会影响到实体子类。
 

@PrePersist注解

在执行给定实体的相应 EntityManager
持久操作之前,调用该实体的 @PrePersist 回调方法。对于向其应用了合并操作并导致创建新管理的实例的实体而言,在向其复制了实体状态后对管理的实例调用该方法。对该操作层叠到的所有实体调用该方法。

如果要在实体生命周期期间的该点调用自定义逻辑,请使用 @PrePersist 批注。

 

@PreUpdate注解

在对实体数据进行数据库更新操作之前,调用实体的 @PreUpdate 回调方法。这些数据库操作可以在更新实体状态时发生,也可以在将状态刷新到数据库(可能位于事务结尾)时发生。请注意:此回调是否在持久保存实体并随后在单个事务中修改该实体时发生,均依赖于实施。可移植应用程序不应依赖此行为。

如果要在实体生命周期的该点调用自定义逻辑,请使用 @PreUpdate 批注。

 

Jackson相关

http://blog.csdn.net/u012373815/article/details/52266609

 

使用Jackson相关的注解时一定要注意自己定义的属性命名是否规范。 
命名不规范时会失去效果。(例如Ename
,Eage 为不规范命名。“nameE”,“ageE”为规范命名)我在此处掉坑半个小时。至于命名规范,大家自己搜索。其实不要太奇葩的命名都是可以得。

如果使用@JsonIgnore注解不起效时请注意一下你的属性名字是否规范

1、@JsonIgnoreProperties

此注解是类注解,作用是json序列化时将Java bean中的一些属性忽略掉,序列化和反序列化都受影响。

写法将此标签加在model
类的类名上 ,可以多个属性也可以单个属性

//生成json时将name和age属性过滤

@JsonIgnoreProperties({"name"},{"age"})

public class  user {

private  String name;

private int age;

}

2、@JsonIgnore

此注解用于属性或者方法上(最好是属性上),作用和上面的@JsonIgnoreProperties一样。

生成json 时不生成age
属性 

public class user { 

private String name; 

@JsonIgnore 

private int age; 



3、@JsonFormat

此注解用于属性或者方法上(最好是属性上),可以方便的把Date类型直接转化为我们想要的模式,比如@JsonFormat(pattern =
“yyyy-MM-dd HH-mm-ss”)

4、@JsonSerialize

此注解用于属性或者getter方法上,用于在序列化时嵌入我们自定义的代码,比如序列化一个double时在其后面限制两位小数点。

5、@JsonDeserialize

此注解用于属性或者setter方法上,用于在反序列化时可以嵌入我们自定义的代码,类似于上面的@JsonSerialize

6、@Transient

@[email protected]�射,ORM框架将忽略该属性; 

如果一个属性并非数据库表的字段映射,就务必将其标示为@Transient,否则ORM框架默认其注解为@Basic;

//表示该字段在数据库表中没有

@Transient 

public int getAge() { 

return 1+1; 

}

参考:http://www.bubuko.com/infodetail-264724.html

 

实体关系映射(一对多@OneToMany)

一对多模型(单向)
说明: 一个客户对应多个地址,通过客户可以获得该客户的多个地址的信息。客户和地址是一对多的关系,并且客户与地址是单向关联的关系。

映射策略

# 外键关联:两个表的关系定义在一个表中;

# 表关联:两个表的关系单独定义一个表中通过一个中间表来关联。

映射策略——外键关联

 

 

@OneToMany、@ManyToOne以及@ManyToMany讲解

一、一对多(@OneToMany)
1、单向一对多模型
假设通过一个客户实体可以获得多个地址信息。
对于一对多的实体关系而言,表结构有两种设计策略,分别是外键关联和表关联。
(1) 映射策略---外键关联
在数据库中表customer和表结构address定义,如下:
create table customer (
  id int(20) not null auto_increment,
  name varchar(100),
  primary key(id)
)
create table address (
  id int(20) not null auto_increment,
  province varchar(50),
  city varchar(50),
  postcode varchar(50),
  detail varchar(50),
  customer_id int(20),
  primary key (id)
)
注意此时外键定义在多的一方,也就是address表中。
 此时,表customer映射为实体CustomerEO,代码如下:
@Entity
@Table(name="customer")
public class CustomerEO implements java.io.Serializable {
  @OneToMany(cascade={ CascadeType.ALL })
  @JoinColumn(name="customer_id")
  private Collection<AddressEO> addresses = new ArrayList<AddressEO>();
...
}
注释@OneToMany的定义代码如下:

@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface OneToMany {
  Class targetEntity() default void.class;
  CascadeType[] cascade() default {};
  FetchType fetch() default LAZY;
  String mappedBy() default "";
}

使用时要注意一下几点问题: 
a、targetEntity属性表示默认关联的实体类型。如果集合类中指定了具体类型了,不需要使用targetEntity.否则要指定targetEntity=AddressEO.class。 
b、mappedBy属性用于标记当实体之间是双向时使用。 

(2) 映射策略---表关联 
在上面address表中去掉customer_id字段,在增加一个表ref_customer_address,如下: --客户地址关系表
create table ref_customer_address (
  customer_id int(20) not null,
  address_id int(20) not null unique
)

此时表customer映射为CustomerEO实体,代码如下:

@Entity
@Table(name = "customer")
public class CustomerEO implements java.io.Serializable {
  ...
  @OneToMany(cascade = { CascadeType.ALL })
  @JoinTable(name="ref_customer_address",
           joinColumns={ @JoinColumn(name="customer_id",referencedColumnName="id")},
           inverseJoinColumns={@JoinColumn(name="address_id",referencedColumnName="id")})
  private Collection<AddressEO> addresses = new ArrayList<AddressEO>();
  ...
}

表关联@JoinTable,定义如下:

@Target({METHOD,FIELD}) 
public @interface JoinTable {
  String name() default "";
  String catalog() default "";
  String schema() default "";
  JoinColumn[] joinColumns() default
{};
  JoinColumn[] inverseJoinColumns() default {};
  UniqueConstraint[] uniqueConstraints default
{};
}

其中:
a、该标记和@Table相似,用于标注用于关联的表。
b、name属性为连接两张表的表名。默认的表名为:“表名1”+“-”+“表名2”,上面例子默认的表名为customer_address。
c、joinColumns属性表示,在保存关系中的表中,所保存关联的外键字段。
d、inverseJoinColumns属性与joinColumns属性类似,不过它保存的是保存关系的另一个外键字段。

(3) 默认关联
在数据库底层为两张表添加约束,如下:
create table customer_address (
  customer_id int(20) not null,
  address_id int(20) not null unique
)
alter table customer_address add constraint fk_ref_customer foreign key (customer_id) references customer (id);

alter table customer_address add constraint fk_ref_address foreign key (address_id) references address (id);

这样,在CustomerEO中只需要在标注@OneToMany即可!

二、多对一@ManyToOne
1、单向多对一模型。
(1) 外键关联
配置AddressEO实体如下:
@Entity
@Table(name="address")
public class AddressEO implements java.io.Serializable {

  @ManyToOne(cascade = { CascadeType.ALL })
  @JoinColumn(name="customer_id")
  private CustomerEO customer;

  // ...
}
@ManyToOne定义如下:
@Target({METHOD,FIELD}) @Retention(RUNTIME)
public @interface ManyToOne { Class targetEntity() default void.class;
  CascadeType[] cascade() default {};
  FetchType fatch() default EAGER;
  boolean optional() default true;
}

(2) 默认关联
数据库脚本定义的相关字段的约束,创建外键后,直接使用@ManyToOne
三、高级一对多和多对一映射
即双向关联模型,确定了双向关联后,多的一方AddressEO不变使用@ManyToOne,而CustomerEO实体修改为:
@Entity
@Table(name="customer")
public class CustomerEO {

  @OneToMany(mappedBy="customer")
  private Collection<AddressEO> addresses = new ArrayList<AddressEO>();

  // ...
}
其中,@OneToMany标记中的mappedBy属性的值为AddressEO实体中所引用的CustomerEO实体的属性名。

四、多对多(@ManyToMany)
和一对多类型,不在赘述。@ManyToMany标记的定义如下:
@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface ManyToMany { Class targetEntity() default void.class;
  CascadeType[] cascade() default {};
  FetchType fecth() default LAZY;
  String mappedBy() default "";
}

五、最后,谈谈关于集合类的选择
在映射关系中可以使用的集合类有Collection、Set、List和Map,下面看下如何选择。
1、定义时使用接口,初始化使用具体的类。
如Collection可以初始化为ArrayList或HashSet;
Set可以初始化为HashSet;
List可以初始化为ArrayList;
Map可以初始化为HashMap.
2、集合类的选择
Collection类是Set和List的父类,在未确定使用Set或List时可使用;
Set集合中对象不能重复,并且是无序的;
List集合中的对象可以有重复,并且可以有排序;
Map集合是带有key和value值的集合。
 

 

 

 

 

 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: