@caos
2014-08-23T13:04:41.000000Z
字数 20949
阅读 3259
编程
根据SpringSide4中涉及到的ORM框架,针对具体实践进行学习,做出比较和总结,以便在实际系统架构设计中能够更好地选择。
- Spring Data :提供了一整套数据访问层(DAO)的解决方案,致力于减少数据访问层(DAO)的开发量。它使用一个叫作Repository的接口类为基础,它被定义为访问底层数据模型的超级接口。而对于某种具体的数据访问操作,则在其子接口中定义。
- 所有继承这个接口的interface都被spring所管理,此接口作为标识接口,功能就是用来控制domain模型的。
- Spring Data可以让我们只定义接口,只要遵循spring data的规范,就无需写实现类。
public interface UserDao extends Repository<AccountInfo, Long> {public AccountInfo save(AccountInfo accountInfo);}
Spring Data JPA提供了四种不同的接口来满足不同的需求
- Repository 接口
默认只实现在接口中声明的方法,所以每一个接口都需要声明相似的增删改查方法,适用于严格控制查询方法的情况。
CrudRepository 接口
能够默认的实现增删改查的持久化方法,适用于常规的持久化方法实现。
PagingAndSortingRepository 接口
继承自 CrudRepository 接口,在 CrudRepository 基础上新增了两个与分页有关的方法。更灵活的方式是继承 Repository 或 CrudRepository 的基础上,在自己声明的方法参数列表最后增加一个 Pageable 或 Sort 类型的参数。
JpaRepository 接口
继承自 PagingAndSortingRepository 的针对 JPA 技术提供的接口,并提供了其他一些方法,比如 flush(),saveAndFlush(),deleteInBatch() 等。
在创建查询时,我们通过在方法名中使用属性名称来表达,比如 findByUserAddressZip ()。框架在解析该方法时,首先剔除 findBy,然后对剩下的属性进行解析,详细规则如下(此处假设该方法针对的域对象为 AccountInfo 类型):
接着处理剩下部分( AddressZip ),先判断 user 所对应的类型是否有 addressZip 属性,如果有,则表示该方法最终是根据 "AccountInfo.user.addressZip" 的取值进行查询;否则继续按照步骤 2 的规则从右往左截取,最终表示根据 "AccountInfo.user.address.zip" 的值进行查询。
同时也考虑到了重复属性的情况,比如 AccountInfo 包含一个 user 的属性,也有一个 userAddress 属性,此时会存在混淆。读者可以明确在属性之间加上"_"以显式表达意图,比如 "findByUser_AddressZip()" 或者 "findByUserAddress_Zip()"。
在查询时,通常需要同时根据多个属性进行查询,且查询的条件也格式各样(大于某个值、在某个范围等等),Spring Data JPA 为此提供了一些表达条件查询的关键字,大致如下:
@Query 注解的使用非常简单,只需在声明的方法上面标注该注解,同时提供一个 JP QL(HQL) 查询语句即可,如下所示:
// 使用 @Query 提供自定义查询语句示例public interface UserDao extends Repository<AccountInfo, Long> {@Query("select a from AccountInfo a where a.accountId = ?1")public AccountInfo findByAccountId(Long accountId);@Query("select a from AccountInfo a where a.balance > ?1")public Page<AccountInfo> findByBalanceGreaterThan(Integer balance,Pageable pageable);}
很多开发者在创建 JP QL 时喜欢使用命名参数来代替位置编号,@Query 也对此提供了支持。JP QL 语句中通过": 变量"的格式来指定参数,同时在方法的参数前面使用 @Param 将方法参数与 JP QL 中的命名参数对应,示例如下:
public interface UserDao extends Repository<AccountInfo, Long> {public AccountInfo save(AccountInfo accountInfo);@Query("from AccountInfo a where a.accountId = :id")public AccountInfo findByAccountId(@Param("id")Long accountId);@Query("from AccountInfo a where a.balance > :balance")public Page<AccountInfo> findByBalanceGreaterThan(@Param("balance")Integer balance,Pageable pageable);}
此外,开发者也可以通过使用 @Query 来执行一个更新操作,为此,我们需要在使用 @Query 的同时,用 @Modifying 来将该操作标识为修改查询,这样框架最终会生成一个更新的操作,而非查询。如下所示:
@Modifying@Query("update AccountInfo a set a.salary = ?1 where a.salary < ?2")public int increaseSalary(int after, int before);
public interface UserDao extends Repository<AccountInfo, Long> {......public List<AccountInfo> findTop5();}
如果希望为 findTop5() 创建命名查询,并与之关联,我们只需要在适当的位置定义命名查询语句,并将其命名为 "AccountInfo.findTop5",框架在创建代理类的过程中,解析到该方法时,优先查找名为 "AccountInfo.findTop5" 的命名查询定义,如果没有找到,则尝试解析方法名,根据方法名字创建查询。
@NamedQuery(name="AccountInfo.findTop5",query="SELECT u FROM User u"),
默认情况下,Spring Data JPA 实现的方法都是使用事务的。针对查询类型的方法,其等价于 @Transactional(readOnly=true);增删改类型的方法,等价于 @Transactional。可以看出,除了将查询的方法设为只读事务外,其他事务属性均采用默认值。
也可以在接口方法上使用 @Transactional 显式指定事务属性,该值覆盖 Spring Data JPA 提供的默认值。
有些时候,开发者可能需要在某些方法中做一些特殊的处理,此时自动生成的代理对象不能完全满足要求。为了享受 Spring Data JPA 带给我们的便利,同时又能够为部分方法提供自定义实现,我们可以采用如下的方法:
<jpa:repositories base-package="footmark.springdata.jpa.dao"><jpa:repository id="accountDao" repository-impl-ref=" accountDaoPlus " /></jpa:repositories><bean id="accountDaoPlus" class="......."/>
提供了一个 repository-impl-postfix 属性,用以指定实现类的后缀。
<jpa:repositories base-package="footmark.springdata.jpa.dao"repository-impl-postfix="Impl"/>
则在框架扫描到 AccountDao 接口时,它将尝试在相同的包目录下查找 AccountDaoImpl.java,如果找到,便将其中的实现方法作为最终生成的代理类中相应方法的实现。
Spring Data JPA 能够和Spring无缝集成,但不依赖Spring,能够作为更好的ORM框架来使用,通过用持久层继承不同的接口,Spring Data JPA能够帮助开发人员免去为持久层接口编写实现类的工作,同时多样话的接口、查询方法名解析与灵活的注解,都能够满足简单到复杂业务的持久化需求。
<dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-jpa</artifactId><version>1.4.3.RELEASE</version><exclusions><exclusion><groupId>junit</groupId><artifactId>junit-dep</artifactId></exclusion></exclusions></dependency>
下载Spring Data JPA 的发布包(需要同时下载 Spring Data Commons 和 Spring Data JPA 两个发布包,Commons 是 Spring Data 的公共基础包),并把相关的依赖 JAR 文件加入到 CLASSPATH 中。
并在Spring配置文件中添加如下配置
<-- 需要在 <beans> 标签中增加对 jpa 命名空间的引用 --><jpa:repositories base-package="footmark.springdata.jpa.dao"entity-manager-factory-ref="entityManagerFactory"transaction-manager-ref="transactionManager"/>
Hibernate提供了一套JPA标准的实现,拥有强大的持久化方法,简化开发人员在持久层的编码工作,同时也提供了完善的缓存的机制,帮助开发者更好地进行复杂业务逻辑的持久化操作。
Hibernate/JPA不但实现了Java的注解接口,也加入了一些扩展的注解。
Hibernate通过三个组件来实现JPA:
实体共有4种状态:
1.在classpath根部META-INF目录下创建persistence.xml文件,内容如下:
<persistence 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"version="1.0"><persistence-unit name="course"><properties><property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/><property name="hibernate.connection.driver_class" value="org.gjt.mm.mysql.Driver"/><property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/db_jpa"/><property name="hibernate.connection.username" value="root"/><property name="hibernate.connection.password" value="root"/><property name="hibernate.show_sql" value="true"/><property name="hibernate.hbm2ddl.auto" value="update"/></properties></persistence-unit></persistence>
2.创建实体类,并加上JPA注解(也可以使用XML形式的注解),代码如下:
@Entity@Table(name="t_course")public class Course {@Id@GeneratedValueprivate Long id;private String title;private Date beginDate;private Date endDate;private int fee;//省略get、set方法}
3.编写Dao接口,代码如下:
public interface CourseDao {public void save(Course course);public void delete(Long id);public Course get(Long id);}
4.编写Dao接口的JPA实现类,代码如下:
public class JpaCourseDao implements CourseDao {//实体对象由实体管理器进行管理,通过EntityManager和持久化上下文进行交互private EntityManagerFactory entityManagerFactory;public JpaCourseDao() {//根据persistence.xml中定义的PersistenceUnit的命名来创建实体管理器entityManagerFactory = Persistence.createEntityManagerFactory("course");}@Overridepublic void delete(Long id) {EntityManager manager = entityManagerFactory.createEntityManager();EntityTransaction tx = manager.getTransaction();try {tx.begin();Course course = manager.find(Course.class, id);manager.remove(course);tx.commit();} catch (RuntimeException e) {tx.rollback();throw e;} finally {manager.close();}}@Overridepublic Course get(Long id) {EntityManager manager = entityManagerFactory.createEntityManager();try {return manager.find(Course.class, id);} finally {manager.close();}}@Overridepublic void save(Course course) {EntityManager manager = entityManagerFactory.createEntityManager();EntityTransaction tx = manager.getTransaction();try {tx.begin();manager.merge(course);tx.commit();} catch (RuntimeException e) {tx.rollback();throw e;} finally {manager.close();}}}
1.在Spring的applicationContext.xml中引入如下配置
<!-- entityManagerFactory 有以下2种方式提供 --><!-- class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">--><!-- class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">--><bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"><property name="persistenceUnitName" value="Test_PU" /></bean><bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"><property name="entityManagerFactory" ref="entityManagerFactory" /></bean><tx:annotation-driven transaction-manager="transactionManager" />
2.在src的META-INF目录下建立persistence.xml文件
<?xml version="1.0" encoding="UTF-8"?><persistence 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/persistencehttp://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0"><persistence-unit name="Test_PU" transaction-type="RESOURCE_LOCAL"><!-- provider>org.eclipse.persistence.jpa.PersistenceProvider</provider--><provider>org.hibernate.ejb.HibernatePersistence</provider><!-- MYSql 的连接--><properties><property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" /><property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/tester?useUnicode=true&characterEncoding=utf-8" /><property name="hibernate.connection.username" value="tester" /><property name="hibernate.connection.password" value="tester" /><property name="hibernate.connection.provider_class" value="org.hibernate.connection.C3P0ConnectionProvider"/><property name="hibernate.c3p0.max_size" value="100"/><property name="hibernate.c3p0.min_size" value="20"/><property name="hibernate.c3p0.timeout" value="120"/><property name="hibernate.c3p0.max_statements" value="0"/><property name="hibernate.c3p0.idle_test_period" value="120"/><property name="hibernate.c3p0.acquire_increment" value="5 "/><property name="hibernate.cache.provider_class" value="org.hibernate.cache.EhCacheProvider"/><property name="hibernate.cache.use_query_cache" value="false"/><property name="hibernate.show_sql" value="false"/><property name="hibernate.useUnicode" value="true"/><property name="hibernate.characterEncoding" value="utf8"/></properties></persistence-unit></persistence>
3.建立TestaService.java,实现了findbyID 和add方法以及一个JPQL的用法。
//此处定义为全部的服务方法都由事物控制@Transactional@Service("com.alcor.test.service.TestaService")public class TestaService {/*** Logger for this class*/private static final Logger logger = Logger.getLogger(TestaService.class);@Autowiredprivate EchoService echoService;@PersistenceContextEntityManager em;//覆盖默认的事物配置@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)public Testa findByID(TestaPK id) {if (logger.isDebugEnabled()) {logger.debug("findByID(TestaPK) - start");}logger.debug(id.getId()+"|"+id.getName());Testa returnTesta = em.find(Testa.class, id);if (logger.isDebugEnabled()) {logger.debug("findByID(TestaPK) - end");}return returnTesta;}//使用了默认的事物机制public void add(){if (logger.isDebugEnabled()) {logger.debug("add() - start");}//保存A表Testa testa = new Testa();TestaPK testaPK = new TestaPK();testaPK.setId(UUID.randomUUID().toString());testaPK.setName(UUID.randomUUID().toString());testa.setId(testaPK);em.persist(testa);//保存B表Testb testb = new Testb();testb.setId(UUID.randomUUID().toString());em.persist(testb);//调用一个autowired 的serviceechoService.doNothing();if (logger.isDebugEnabled()) {logger.debug("add() - end");}}/*** 通过使用JPQL 来做查询*/@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)public List<Testa> findAllBySex (){if (logger.isDebugEnabled()) {logger.debug("findAllBySex() - start");}String queryString = "SELECT a FROM Testa a WHERE a.age < :age AND a.id.name like :name";Query query = em.createQuery(queryString);query.setParameter("name", "%xv%");query.setParameter("age", 20);List<Testa> results = query.getResultList();if (logger.isDebugEnabled()) {logger.debug("findAllBySex() - end");}return results;}}
Hibernate/JPA在用法上根据不同的集成方式而改变,但其核心的功能都是一样的,其重点是实现了JPA的标准ORM规范,使持久层的操作与具体的框架解耦。
它在Hibernate里写法上是session,而在JPA中变成了manager,所以从Hibernate到JPA的代价应该是非常小的。
MyBatis 是支持普通 SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis 消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。MyBatis 使用简单的XML或注解用于配置和原始映射,将接口和 Java 的POJOs(Plain Old Java Objects,普通的 Java
对象)映射成数据库中的记录。
每个MyBatis应用程序主要都是使用SqlSessionFactory实例的,一个SqlSessionFactory实例可以通过SqlSessionFactoryBuilder获得。SqlSessionFactoryBuilder可以从一个xml配置文件或者一个预定义的配置类的实例获得。
MyBatis在不同的框架环境下有不同的配置方式,但具体的步骤如下:
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><environments default="development"><environment id="development"><transactionManager type="JDBC" /><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver" /><property name="url" value="jdbc:mysql://localhost:3306/hlp?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull" /><property name="username" value="root" /><property name="password" value="1234" /></dataSource></environment></environments><mappers><mapper resource="com/mybatis/config/UserDaoMapper.xml"/></mappers></configuration>
package com.mybatis.config;public class User {private int userId;private String userName;private String password;private String comment;//省略get/set方法}
package com.mybatis.config;import java.util.List;public interface UserDao {public int insert(User user);public int update(User user);public int delete(String userName);public List<User> selectAll();public int countAll();public User findByUserName(String userName);}
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.mybatis.config.UserDao"><select id="countAll" resultType="int">select count(*) c from user;</select><select id="selectAll" resultType="com.mybatis.config.User">select * from user order by userName asc</select><insert id="insert" parameterType="com.mybatis.config.User">insert into user(userName,password,comment) values(#{userName},#{password},#{comment})</insert><update id="update" parameterType="com.mybatis.config.User">update user set userName=#{userName},password=#{password},comment=#{comment} where userName=#{userName}</update><delete id="delete" parameterType="int">delete from user where userName=#{userName}</delete><select id="findByUserName" parameterType="String" resultType="com.mybatis.config.User">select * from user where userName=#{userName}</select></mapper>
package com.mybatis.config;import java.io.Reader;import java.util.Iterator;import java.util.List;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import org.junit.Test;public class UserDaoTest {@Testpublic void userDaoTest() throws Exception{String resource = "MyBatis-Configuration.xml";Reader reader = Resources.getResourceAsReader(resource);SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();SqlSessionFactory factory = builder.build(reader);SqlSession session = factory.openSession();UserDao userDao = session.getMapper(UserDao.class);User user = new User();user.setUserName("hongye");user.setPassword("123456");user.setComment("备注");userDao.insert(user);System.out.println("记录条数:"+userDao.countAll());List<User> users = userDao.selectAll();Iterator<User> iter = users.iterator();while(iter.hasNext()){User u = iter.next();System.out.println("用户名:"+u.getUserName()+"密码:"+u.getPassword());}user.setComment("comment");userDao.update(user);User u = userDao.findByUserName("hongye");System.out.println(u.getComment());userDao.delete("hongye");System.out.println("记录条数:"+userDao.countAll());session.commit();session.close();}}
根据MyBatis的使用步骤,按照SpringSide4中的最佳实践,给出与Spring整合的方案
<!-- mybatis --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.2.3</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>1.2.1</version></dependency>
<!-- MyBatis配置 --><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSource" /><!-- 自动扫描entity目录, 省掉Configuration.xml里的手工配置 --><property name="typeAliasesPackage" value="org.springside.examples.showcase.entity" /><!-- 显式指定Mapper文件位置 --><property name="mapperLocations" value="classpath:/mybatis/*Mapper.xml" /></bean><!-- 扫描basePackage下所有以@MyBatisRepository标识的 接口--><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="org.springside.examples.showcase" /><property name="annotationClass" value="org.springside.examples.showcase.repository.mybatis.MyBatisRepository"/></bean>
//通过@MapperScannerConfigurer扫描目录中的所有接口,动态在Spring Context中生成实现.@MyBatisRepositorypublic interface UserMybatisDao {User get(Long id);List<User> search(Map<String, Object> parameters);void save(User user);void delete(Long id);}
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!-- namespace必须指向Dao接口 --><mapper namespace="org.springside.examples.showcase.repository.mybatis.UserMybatisDao"><!--获取用户: 输出直接映射到对象, login_name列要"as loginName"以方便映射 ,team_id as "team.id"创建team对象并赋值--><select id="get" parameterType="int" resultType="User">select id, name, email,login_name as loginName,team_id as "team.id"from ss_userwhere id=#{id}</select><!-- 查询用户,演示: 1.输入用map传入多个参数 2.<where>语句, 智能添加where和and关键字 3.输出直接映射对象 --><select id="search" parameterType="map" resultType="User">select id, name, email,login_name as loginName,team_id as "team.id"from ss_user<where><if test="loginName != null">login_name=#{loginName}</if><if test="name != null">and name=#{name}</if></where></select><!-- 插入用户: 1.由数据库生成id并赋值到user对象 2.输入用对象, 嵌套属性表达式#{team.id} --><insert id="save" parameterType="User" useGeneratedKeys="true" keyProperty="id">insert into ss_user (login_name, name, password, email, team_id)values (#{loginName}, #{name}, #{password}, #{email},#{team.id})</insert><!-- 删除用户 --><delete id="delete" parameterType="int">delete from ss_user where id=#{id}</delete></mapper>
@DirtiesContext@ContextConfiguration(locations = { "/applicationContext.xml" })public class UserMybatisDaoTest extends SpringTransactionalTestCase {@Autowiredprivate UserMybatisDao userDao;@Testpublic void getUser() throws Exception {User user = userDao.get(1L);assertNotNull("User not found", user);assertEquals("admin", user.getLoginName());}@Testpublic void searchUser() throws Exception {Map<String, Object> parameter = Maps.newHashMap();parameter.put("name", "管理员");List<User> result = userDao.search(parameter);assertEquals(1, result.size());assertEquals("admin", result.get(0).getLoginName());}@Testpublic void createAndDeleteUser() throws Exception {// createint count = countRowsInTable("ss_user");User user = UserData.randomUser();userDao.save(user);Long id = user.getId();assertEquals(count + 1, countRowsInTable("ss_user"));User result = userDao.get(id);assertEquals(user.getLoginName(), result.getLoginName());// deleteuserDao.delete(id);assertEquals(count, countRowsInTable("ss_user"));assertNull(userDao.get(id));}}
iBATIS 的着力点,则在于POJO与SQL之间的映射关系。然后通过映射配置文件,将SQL所需的参数,以及返回的结果字段映射到指定POJO。相对Hibernate“O/R”而言,iBATIS 是一种“Sql Mapping”的ORM实现。
MyBatis的学习难度相对较低,但需要手写大量的sql语句,且不如Hibernate封装性强,适合使用在对于没有那么高的对象模型要求的项目。
具体与Hibernate的比较参考Hibernate与 MyBatis的比较