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

Posted hoo334

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用对象-关系映射持久化数据相关的知识,希望对你有一定的参考价值。

在数据持久化的世界中,JDBC就像自行车,对于份内的工作它能完成的很好。随着应用程序越来越复杂,对持久化的要求也越来越复杂。我们需要将对象的属性映射到数据库的列上,并且需要自动生成语句和查询,这样我们就能从无休止的问号字符串中解脱出来。

一、Hibernate

还是以Spittr应用为例来具体阐述Hibernate的使用。

我们定义Spitter、Spittle两个实体类。

package spittr.domain;

import javax.persistence.Column;

import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.GenerationType;

import javax.persistence.Id;

@Entity

public class Spitter {

private Spitter() {}

@Id

@GeneratedValue(strategy=GenerationType.IDENTITY)

private Long id;

  @Column(name="username")

private String username;

  @Column(name="password")

private String password;

  @Column(name="fullname")

private String fullName;

  @Column(name="email")

private String email;

  @Column(name="updateByEmail")

private boolean updateByEmail;

  public Spitter(Long id, String username, String password, String fullName,

        String email, boolean updateByEmail) {

this.id = id;

      this.username = username;

      this.password = password;

      this.fullName = fullName;

      this.email = email;

      this.updateByEmail = updateByEmail;

  }

public Long getId() {return id;}

public String getUsername() {return username;}

public String getPassword() {return password; }

public String getFullName() {return fullName;}

public String getEmail() {return email;}

public boolean isUpdateByEmail() {return updateByEmail; }

}

 

package spittr.domain;

import java.util.Date;

import javax.persistence.Column;

import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.GenerationType;

import javax.persistence.Id;

import javax.persistence.JoinColumn;

import javax.persistence.ManyToOne;

@Entity

public class Spittle {

private Spittle() {}

@Id

  @GeneratedValue(strategy=GenerationType.IDENTITY)

private Longid;

  @ManyToOne

  @JoinColumn(name="spitter")

private Spitter spitter;

  @Column

  private String message;

  @Column

  private Date postedTime;

  public Spittle(Long id, Spitter spitter, String message, Date postedTime) {

this.id = id;

      this.spitter = spitter;

      this.message = message;

      this.postedTime = postedTime;

  }

public Long getId() {

return this.id;

  }

public String getMessage() {

return this.message;

  }

public Date getPostedTime() {

return this.postedTime;

  }

public Spitter getSpitter() {

return this.spitter;

  }

}

@Entity注解表示这是一个实体类,@Id注解代表这是数据表的primary key ,@GeneratedValue代表这是自动生成的列,

@Column注解将数据表的一列与类的属性绑定,不指定name属性则代表和变量名相同,@ManyToOne注解代表会有多个Spittle参照同一个Spitter, @JoinColumn注解代表参考spitter列。

这样就实现了类属性和数据表项的绑定。

同样我们定义SpitterRepository、SpittleRepository接口。

package spittr.db;

import java.util.List;

import spittr.domain.Spittle;

public interface SpittleRepository {

long count();

  List findRecent();

  List findRecent(int count);

  Spittle findOne(long id);

  Spittle save(Spittle spittle);

  List findBySpitterId(long spitterId);

  void delete(long id);

}

 

package spittr.db;

import java.util.List;

import spittr.domain.Spitter;

public interface SpitterRepository {

long count();

  Spitter save(Spitter spitter);

  Spitter findOne(long id);

  Spitter findByUsername(String username);

  List findAll();

}

我们先看看如何配置Hibernate 下面是RepositoryTestConfig.java。

package spittr.db.hibernate4;

import java.io.IOException;

import java.util.Properties;

import javax.inject.Inject;

import javax.sql.DataSource;

import org.hibernate.SessionFactory;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.ComponentScan;

import org.springframework.context.annotation.Configuration;

import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;

import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;

import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;

import org.springframework.orm.hibernate4.HibernateTransactionManager;

import org.springframework.orm.hibernate4.LocalSessionFactoryBean;

import org.springframework.transaction.PlatformTransactionManager;

import org.springframework.transaction.annotation.EnableTransactionManagement;

import org.springframework.transaction.annotation.TransactionManagementConfigurer;

@Configuration

@EnableTransactionManagement

@ComponentScan

public class RepositoryTestConfigimplements TransactionManagementConfigurer {

@Inject

  private SessionFactorysessionFactory;

  @Bean

  public DataSource dataSource() {

EmbeddedDatabaseBuilder edb =new EmbeddedDatabaseBuilder();

    edb.setType(EmbeddedDatabaseType.H2);

    edb.addScript("spittr/db/hibernate4/schema.sql");

    edb.addScript("spittr/db/hibernate4/test-data.sql");

    EmbeddedDatabase embeddedDatabase = edb.build();

    return embeddedDatabase;

  }

public PlatformTransactionManager annotationDrivenTransactionManager() {

System.out.println(sessionFactory);

    HibernateTransactionManager transactionManager =new HibernateTransactionManager();

    transactionManager.setSessionFactory(sessionFactory);

    return transactionManager;

  }

@Bean

  public SessionFactory sessionFactoryBean() {

try {

LocalSessionFactoryBean lsfb =new LocalSessionFactoryBean();

      lsfb.setDataSource(dataSource());

      lsfb.setPackagesToScan("spittr.domain");

      Properties props =new Properties();

      props.setProperty("dialect", "org.hibernate.dialect.H2Dialect");

      lsfb.setHibernateProperties(props);

      lsfb.afterPropertiesSet();

      SessionFactory object = lsfb.getObject();

      return object;

    }catch (IOException e) {

return null;

    }

}

}

这里我们声明它是一个配置类并且启用了组件扫描和事务管理。

@Inject和@Autowired基本相同,同样dataBase bean为数据源配置,annotationDrivenTransactionManager bean 为事务处理,

使用Hibernate所需要的主要接口时org.hibernate.Session。Session接口提供了基本的数据访问功能,如保存、更新、删除以及从数据库加载对象的功能。通过Hibernate的Session接口,应用程序的Repository能够满足所有的持久化需求。

SessionFactory主要负责Hibernate Session的打开,关闭以及管理。

dataSource和hibernateProperties属性声明了从哪里获取数据库连接以及要使用哪一种数据库。这里不再列出Hibernate配置文件,使用packageToScan属性告诉Spring扫描一个或多个包以查找域类,这些类表明要使用Hibernate进行持久化,这些类可以使用的注解包括JPA的@Entity或@MappedSuperclass以及Hibernate的Entity。

了解完Hibernate的配置之后我们开始编写HibernateSpitterRepository和HibernateSpittleRepository他们分别实现SpitterRepository接口和SpittleRepository接口。

package spittr.db.hibernate4;

import java.io.Serializable;

import java.util.List;

import javax.inject.Inject;

import org.hibernate.Session;

import org.hibernate.SessionFactory;

import org.hibernate.criterion.Restrictions;

import org.springframework.stereotype.Repository;

import spittr.db.SpitterRepository;

import spittr.domain.Spitter;

@Repository

public class HibernateSpitterRepository implements SpitterRepository {

private SessionFactory sessionFactory;

@Inject

public HibernateSpitterRepository(SessionFactory sessionFactory) {

this.sessionFactory = sessionFactory; //<co id="co_InjectSessionFactory"/>

}

private Session currentSession() {

return sessionFactory.getCurrentSession();//<co id="co_RetrieveCurrentSession"/>

}

public long count() {

return findAll().size();

}

public Spitter save(Spitter spitter) {

Serializable id = currentSession().save(spitter);  //<co id="co_UseCurrentSession"/>

return new Spitter((Long) id,

spitter.getUsername(),

spitter.getPassword(),

spitter.getFullName(),

spitter.getEmail(),

spitter.isUpdateByEmail());

}

public Spitter findOne(long id) {

return (Spitter) currentSession().get(Spitter.class, id);

}

public Spitter findByUsername(String username) {

return (Spitter) currentSession()

.createCriteria(Spitter.class)

.add(Restrictions.eq("username", username))

.list().get(0);

}

public List<Spitter> findAll() {

return (List<Spitter>) currentSession()

.createCriteria(Spitter.class).list();

}

}

 

package spittr.db.hibernate4;

import java.io.Serializable;

import java.util.List;

import javax.inject.Inject;

import org.hibernate.Criteria;

import org.hibernate.Session;

import org.hibernate.SessionFactory;

import org.hibernate.criterion.Order;

import org.hibernate.criterion.Restrictions;

import org.springframework.stereotype.Repository;

import spittr.db.SpittleRepository;

import spittr.domain.Spittle;

@Repository

public class HibernateSpittleRepositoryimplements SpittleRepository {

private SessionFactorysessionFactory;

  @Inject

  public HibernateSpittleRepository(SessionFactory sessionFactory) {

this.sessionFactory = sessionFactory;

  }

private Session currentSession() {

return sessionFactory.getCurrentSession();//

  }

public long count() {

return findAll().size();

  }

public List findRecent() {

return findRecent(10);

  }

public List findRecent(int count) {

return (List) spittleCriteria()

.setMaxResults(count)

.list();

  }

public Spittle findOne(long id) {

return (Spittle) currentSession().get(Spittle.class, id);

  }

public Spittle save(Spittle spittle) {

Serializable id = currentSession().save(spittle);

      return new Spittle(

(Long) id,

        spittle.getSpitter(),

        spittle.getMessage(),

        spittle.getPostedTime());

  }

public List findBySpitterId(long spitterId) {

return spittleCriteria()

.add(Restrictions.eq("spitter.id", spitterId))

.list();

  }

public void delete(long id) {

currentSession().delete(findOne(id));

  }

public List findAll() {

return (List) spittleCriteria().list();

  }

private Criteria spittleCriteria() {

return currentSession()

.createCriteria(Spittle.class)

.addOrder(Order.desc("postedTime"));

  }

}

我们将一个SessionFactory注入到HibernateSpitterRepository和HibernateSpittleRepository的sessionFactory属性中,在currentSession()方法中我们使用这个sessionFactory来获取当前事务的Session。

我们使用了@Repository注解,它能够被组件扫描扫描到,不必显示声明,它还会捕获平台相关的异常,然后使用Spring统一非检查型异常重新抛出。

数据类同数据库的表存在对应关系,使用Hibernate操作数据类时,Hibernate会将之转换为对数据库中对应表的操作。

org.hibernate.Criteria接口表示特定持久类的一个查询。Session是 Criteria实例的工厂。currentSession()

.createCriteria(Spittle.class)表示Spittle类的一个查询。参见:http://www.baike.com/wiki/criteria

session.save()方法返回一个生成的id,该id为Serializable类型。

接下来测试一下

package spittr.db.hibernate4;

import static org.junit.Assert.*;

import java.util.List;

import org.junit.BeforeClass;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.test.context.ContextConfiguration;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import org.springframework.transaction.annotation.Transactional;

import spittr.db.SpitterRepository;

import spittr.domain.Spitter;

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(classes = RepositoryTestConfig.class)

public class SpitterRepositoryTest {

@Autowired

  SpitterRepository spitterRepository;

@Test

@Transactional

  public void findAll() {

List spitters =spitterRepository.findAll();

    assertEquals(4, spitters.size());

    assertSpitter(0, spitters.get(0));

    assertSpitter(1, spitters.get(1));

    assertSpitter(2, spitters.get(2));

    assertSpitter(3, spitters.get(3));

  }

}

二、JPA-Hibernate

我们尝试开发基于JPA的Repository。Java持久化API(Java Persistence API,JPA)诞生在EJB2实体Bean的废墟之上,并成为下一代Java持久化标准。JPA是基于POJO的持久化机制。

以Spittr应用为例,我们首先看看JPAConfig类

package spittr.db.jpa;

import javax.inject.Inject;

import javax.persistence.EntityManagerFactory;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.ComponentScan;

import org.springframework.context.annotation.Configuration;

import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;

import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;

import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;

import org.springframework.orm.jpa.JpaTransactionManager;

import org.springframework.orm.jpa.JpaVendorAdapter;

import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;

import org.springframework.orm.jpa.vendor.Database;

import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;

import org.springframework.transaction.PlatformTransactionManager;

import org.springframework.transaction.annotation.EnableTransactionManagement;

import org.springframework.transaction.annotation.TransactionManagementConfigurer;

@Configuration

@ComponentScan

public class JpaConfig {

@Bean

  public DataSource dataSource() {

EmbeddedDatabaseBuilder edb =new EmbeddedDatabaseBuilder();

    edb.setType(EmbeddedDatabaseType.H2);

    edb.addScript("spittr/db/jpa/schema.sql");

    edb.addScript("spittr/db/jpa/test-data.sql");

    EmbeddedDatabase embeddedDatabase = edb.build();

    return embeddedDatabase;

  }

@Bean

  public LocalContainerEntityManagerFactoryBean emf(DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) {

LocalContainerEntityManagerFactoryBean emf =new LocalContainerEntityManagerFactoryBean();

    emf.setDataSource(dataSource);

    emf.setPersistenceUnitName("spittr");

    emf.setJpaVendorAdapter(jpaVendorAdapter);

    emf.setPackagesToScan("spittr.domain");

    return emf;

  }

@Bean

  public JpaVendorAdapter jpaVendorAdapter() {

HibernateJpaVendorAdapter adapter =new HibernateJpaVendorAdapter();

    adapter.setDatabase(Database.H2);

    adapter.setShowSql(true);

    adapter.setGenerateDdl(false);

    adapter.setDatabasePlatform("org.hibernate.dialect.H2Dialect");

    return adapter;

  }

@Configuration

@EnableTransactionManagement

  public static class TransactionConfig implements TransactionManagementConfigurer {

@Inject

    private EntityManagerFactory emf;

    public PlatformTransactionManager annotationDrivenTransactionManager() {

JpaTransactionManager transactionManager =new JpaTransactionManager();

      transactionManager.setEntityManagerFactory(emf);

      return transactionManager;

    }

}

}

基于JPA的应用程序需要使用EntityManagerFactory的实现类来获得EntityManager实例。JPA定义了应用程序管理类型和容器管理类型的实体管理器,这两种实体管理器实现了同一个EntityManager接口。关键的区别不在于EntityManager本身,而是在于EntityManager的创建和管理方式。顾名思义,前者由应用程序创建和管理,后者由Java EE创建和管理。

JpaVendorAdapter 属性用于指明所使用的是哪一个厂商的JPA实现。

使用LocalContainerEntityManagerFactoryBean来配置容器管理类型的JPA。

接下来编写基于JPA的Repository:JpaSpitterRepository、JpaSpittleRepository

package spittr.db.jpa;

import java.util.List;

import javax.persistence.EntityManager;

import javax.persistence.PersistenceContext;

import org.springframework.stereotype.Repository;

import spittr.db.SpitterRepository;

import spittr.domain.Spitter;

@Repository

public class JpaSpitterRepository implements SpitterRepository {

@PersistenceContext

  private EntityManager entityManager;

  public long count() {

return findAll().size();

  }

public Spitter save(Spitter spitter) {

entityManager.persist(spitter);

      return spitter;

  }

public Spitter findOne(long id) {

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

  }

public Spitter findByUsername(String username) {

return (Spitter)entityManager.createQuery("select s from Spitter s where s.username=?").setParameter(1, username).getSingleResult();

  }

public List findAll() {

return (List)entityManager.createQuery("select s from Spitter s").getResultList();

  }

}

 

package spittr.db.jpa;

import java.util.List;

import javax.persistence.EntityManager;

import javax.persistence.PersistenceContext;

import org.springframework.stereotype.Repository;

import spittr.db.SpittleRepository;

import spittr.domain.Spittle;

@Repository

public class JpaSpittleRepository implements SpittleRepository {

@PersistenceContext

  private EntityManager entityManager;

  public long count() {

return findAll().size();

  }

public List findRecent() {

return findRecent(10);

  }

public List findRecent(int count) {

return (List)entityManager.createQuery("select s from Spittle s order by s.postedTime desc")

.setMaxResults(count)

.getResultList();

  }

public Spittle findOne(long id) {

return entityManager.find(Spittle.class, id);

  }

public Spittle save(Spittle spittle) {

entityManager.persist(spittle);

    return spittle;

  }

public List findBySpitterId(long spitterId) {

return (List)entityManager.createQuery("select s from Spittle s, Spitter sp where s.spitter = sp and sp.id=? order by s.postedTime desc")

.setParameter(1, spitterId)

.getResultList();

  }

public void delete(long id) {

entityManager.remove(findOne(id));

  }

public List findAll() {

return (List)entityManager.createQuery("select s from Spittle s").getResultList();

  }

}

由于EntityManager并不是线程安全的,一般并不适合注入到Repository这样共享的单例Bean中,我们使用@PersistanceContext注解解决这个问题。@PersistanceContext并没有注入一个真正的EntityManager,而是给了它一个EntityManager的代理。真正的EntityManager是与当前事务相关联的那一个,如果不存在,就会创建一个新的。@Transactional表明这个Repository中的持久化方法是在事务上下文中执行的。

以下是测试类部分代码:

package spittr.db.jpa;

import static org.junit.Assert.*;

import java.util.List;

import org.junit.BeforeClass;

import org.junit.Ignore;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.test.context.ContextConfiguration;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import org.springframework.transaction.annotation.Transactional;

import spittr.db.SpitterRepository;

import spittr.domain.Spitter;

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(classes=JpaConfig.class)

public class SpitterRepositoryTest {

@Autowired

  SpitterRepository spitterRepository;

@Test

@Transactional

  public void findAll() {

List spitters =spitterRepository.findAll();

      assertEquals(4, spitters.size());

      assertSpitter(0, spitters.get(0));

      assertSpitter(1, spitters.get(1));

      assertSpitter(2, spitters.get(2));

      assertSpitter(3, spitters.get(3));

  }

}

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

尽管JPA-Hibernate代码已经很简单,但依然会有直接与EntityManager交互来查询数据库。我们借助Spring Data,以接口定义的方式创建Repository。

package spittr.db;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

import spittr.domain.Spittle;

 

public interface SpittleRepository extends JpaRepository, SpittleRepositoryCustom {

ListfindBySpitterId(long spitterId);

}

 

package spittr.db;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

import spittr.domain.Spitter;

public interface SpitterRepository extends JpaRepository, SpitterSweeper {

Spitter findByUsername(String username);

List findByUsernameOrFullNameLike(String username, String fullName);

}

这里我们并不需要实现findBySpittleId方法,findByUsername方法,findByUsernameOrFullNameLike方法,方法签名已经告诉Spring Data JPA足够的信息来创建这个方法的实现了。

编写Spring Data JPA Repository 的关键在于要从一组接口中选一个进行扩展,这里SpittleRepository扩展了Spring Data JPA 的JpaRepository。通过这种方式,JpaRepository进行了参数化,所以它就能知道这是一个用来持久化Spitter对象的Repository,并且Spitter对象的ID类型为Long。

接下来我们看看自定义查询方法。

package spittr.db;

public interface Spitter Sweeper {

int eliteSweep();

}

package spittr.db;

import javax.persistence.EntityManager;

import javax.persistence.PersistenceContext;

public class SpitterRepositoryImpl implements SpitterSweeper {

@PersistenceContext

  private EntityManager em;

  public int eliteSweep() {

String update =

"UPDATE Spitter spitter " +

"SET spitter.status = ‘Elite‘ " +

"WHERE spitter.status = ‘Newbie‘ " +

"AND spitter.id IN (" +

"SELECT s FROM Spitter s WHERE (" +

"  SELECT COUNT(spittles) FROM s.spittles spittles) > 10000" +

")";

      return em.createQuery(update).executeUpdate();

  }

}

复杂查询可以自己编写。

 

配置Spring Data  JPA

package spittr.db;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;

import org.springframework.orm.jpa.JpaTransactionManager;

import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;

import org.springframework.orm.jpa.vendor.Database;

import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;

@Configuration

@EnableJpaRepositories("spitter.db")

public class SpringDataJpaConfig {

@Bean

  public DataSourcedataSource() {

return new EmbeddedDatabaseBuilder()

.addScript("classpath:/com/habuma/spitter/db/jpa/schema.sql")

.addScript("classpath:/com/habuma/spitter/db/jpa/test-data.sql")

.build();

  }

@Bean

  public JpaTransactionManagertransactionManager() {

return new JpaTransactionManager(); // does this need an emf???

  }

@Bean

  public HibernateJpaVendorAdapterjpaVendorAdapter() {

HibernateJpaVendorAdapter adapter =new HibernateJpaVendorAdapter();

    adapter.setDatabase(Database.H2);

    adapter.setShowSql(false);

    adapter.setGenerateDdl(true);

    return adapter;

  }

@Bean

  public Objectemf() {

LocalContainerEntityManagerFactoryBean emf =new LocalContainerEntityManagerFactoryBean();

    emf.setDataSource(dataSource());

    emf.setPersistenceUnitName("spitter");

    emf.setJpaVendorAdapter(jpaVendorAdapter());

    return emf;

  }

}

@EnableJpaRepositories("spitter.db")会扫描spittr.db包查找扩展自Spring Data JPA Repository接口的所有接口,如果发现了扩展自Repository的接口,他会自动(在应用启动的时候)生成这个接口的实现。

以上是关于使用对象-关系映射持久化数据的主要内容,如果未能解决你的问题,请参考以下文章

对象关系映射(ORM)

持久化和对象关系映射

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

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

ORM-对象关系映射

Hibernate 对象关系映射文件