使用对象-关系映射持久化数据
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的接口,他会自动(在应用启动的时候)生成这个接口的实现。
以上是关于使用对象-关系映射持久化数据的主要内容,如果未能解决你的问题,请参考以下文章