说到咱们的web开发架构分层中,持久层是相对底层也是相对稳定的一层,奠基好根基后,咱们才能专一于业务逻辑和视图开发。而自从ORM思想蔓延开来后,全自动ORM的Hibernate和半自动ORM的MyBatis几乎垄断了持久层(固然还有不少公司或者大牛本身封装的框架,不过相对占小部分),是发展过程当中比较主流的两款持久层框架。前段时间也关注了不少有关领域驱动设计的内容,感受对前面的传统架构分层冲击较大,尤为是业务逻辑层、持久层、实体ORM那块,引入了许多新概练,一时间也遇到了不少困惑,网上搜索资料发现领域驱动其实由来已久,目前也应用不少,可是想要彻底掌握,并非一件容易事。固然本文跟领域驱动并没有直接关联,如今的问题是在面试题“Hibernate和MyBatis的区别”背景下,咱们在持久层还有第三种典型选择吗,实际上是有的,那就是本文的主角,Spring Data Jpa。 html
提及Jpa,其实它并非一个新概念,更不是说有了Spring Data Jpa才出现,它是Java Persistence API的简称,中文名Java持久层API,它是一种规范,例如Hibernate框架即实现了这种规范,Spring Data Jpa中便集成了Hibernate的模块。Spring Data,看名字很容易知道又是Spring系列的,除了Spring MVC在web层的成功,在持久层这块Spring也想拿下,大有想一统江湖的势头。另外去深刻关注Spring Data内容,还会发现,不只仅是RDBMS,还有Spring Data Redis、Spring Data Mongodb等等...本文内容主要是针对关系型数据库,本人在使用过程当中,最看好的仍是其在通用持久化方面的简易封装,基于层层的泛型继承,咱们能够省略大量的简单增删改查方法的编码,另外提供的经过特定格式方法命名简化方法定义过程也很特别和好用。下面就基于Spring Data编写一个单独的简单持久层实例来展示其使用过程。 java
Eclipse + MySql + Mavenmysql
基于传统几大框架的开发目前已经相对成熟不少了,可是就实际工做开发环境中,笔者最强烈的感觉有一点,配置!配置文件实在太多了!特别是多工程组合集成的时候,漫天飞的XML和properties真是让人头大。因此建议如今学习过程当中必定要尽可能搞懂配置中每段配置语句的含义,哪一个参数有什么做用,这样进入实际开发中才不会一时间无章可循。本文中配置尽可能给出注释来阐述含义。web
在eclipse新建一个普通maven项目,quickstart类型,项目结构大体以下面试
pom.xml依赖以下spring
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!-- Spring 系列 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>4.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-jpa</artifactId> <version>1.9.1.RELEASE</version> </dependency> <!-- Hibernate系列 --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>4.3.11.Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>4.3.11.Final</version> </dependency> <!-- MySQL --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.37</version> </dependency> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.4</version> </dependency> </dependencies>
第一步、配置文件(固然实际开发中,咱们不会将配置这样集中在一个文件中,同时数据源配置等关键参数每每会经过properties文件去设置而后引入)sql
<?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" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- Spring的bean扫描机制,会根据bean注解例如@Service等去实例化相应bean --> <context:component-scan base-package="com.sdj"></context:component-scan> <!-- 这句代码是告诉jpa咱们的持久层接口都在哪些包下面 --> <jpa:repositories base-package="com.sdj.repository"/> <!-- 这里使用dbcp配置数据源,能实现链接池功能 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://192.168.0.100:3306/sdj"/> <property name="username" value="root"/> <property name="password" value="abc123"/> </bean> <!-- 实体管理器工厂配置,关联数据源,指定实体类所在包等等 --> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="packagesToScan" value="com.sdj.domain"/> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <property name="database" value="MYSQL"/> <property name="generateDdl" value="false"/> <property name="showSql" value="true"/> </bean> </property> <property name="jpaProperties"> <props> <prop key="hibernate.hbm2ddl.auto">none</prop> <!-- 若是想要自动生成数据表,这里的配置是关键 --> <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop> <!-- <prop key="hibernate.dialect">org.hibernate.dialect.OracleDialect</prop> --> <prop key="hibernate.connection.charSet">UTF-8</prop> <prop key="hibernate.format_sql">true</prop> </props> </property> </bean> <!--配置事务管理器--> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean> <!--启用事务注解来管理事务--> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>
上面Spring配置文件中,实体管理器工厂配置是比较复杂的部分,下面具体到参数逐个介绍数据库
dataSource,指定数据源apache
packagesToScan,与前面的component-scan相似,这里也是一种扫描机制,不过前面是扫描bean,这里既然是实体管理器,不难理解是扫描实体类,即指定实体类所在的包,这里为com.sdj.domain架构
jpaVendorAdapter,这里对应Jpa持久化实现厂商Hibernate,同时指定其专用特性,包括以下
database,指定数据库,这里为MYSQL
generateDdl,是否自动生成DDL,这里为false
showSql,是否在控制台打印SQL语句,这点在调试时比较有用,能看到具体发送了哪些SQL
jpaProperties,jpa属性设置,有以下
hibernate.hbm2ddl.auto,根据须要能够设置为validate | update | create | create-drop,固然也能够设置为none,设置的时候要当心,使用不到会有丢失数据的危险,例如这里若是咱们想要根据实体类自动生成数据表,能够设置为update,不用的话这里设置为none
hibernate.dialect,指定数据库方言,这里为MySql数据库类型的
hibernate.connection.charSet,指定连接字符编码,解决乱码问题
hibernate.format_sql,前面指定控制台会打印SQL,这里是指定将其格式化打印,更加清晰美观一点
第二步、实体类Person
package com.sdj.domain; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name="TB_PERSON") public class Person { private Integer id; //主键 private String name; //姓名 private String gender; //性别 private String addr; //地址 @Id @GeneratedValue(strategy=GenerationType.AUTO) public Integer getId() { return id; } @Column(name="NAME") public String getName() { return name; } public String getGender() { return gender; } public String getAddr() { return addr; } public void setId(Integer id) { this.id = id; } public void setName(String name) { this.name = name; } public void setGender(String gender) { this.gender = gender; } public void setAddr(String addr) { this.addr = addr; } }
若是仔细观察实体中系列注解,会发现其来源是hibernate-jpa,这也是前面提到的hibernate实现jpa规范内容。经常使用注解解释以下
@Entity,指定该类为一个数据库映射实体类、
@Table,指定与该实体类对应的数据表
@Id和@Column,都是为实体类属性关联数据表字段,区别是Id是对应主键字段,另外还能够指定其对应字段名(不指定默认与属性名一致)、长度等等...若是不加这两个注解也是会以属性名默认关联到数据库,若是不想关联能够加上下面的@Transient
@GeneratedValue,指定主键生成策略
@Transient,表名该属性并不是数据库表的字段映射
@OneToMany、@ManyToOne、@ManyToMany等,均为关联映射注解系列,用于指定关联关系,一对多、多对一等等
另外,@Id、@Column、@Transient等注解每每是加在属性的get方法上。
第三步、持久层接口PersonRepository
package com.sdj.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import com.sdj.domain.Person; public interface PersonRepository extends JpaRepository<Person, Integer> { List<Person> findByName(String name); }
咱们发现这里持久层代码反而是最简洁的,咱们的注意点以下:
1.在这个针对Person实体的dao接口中咱们并未定义常规通用的那些增删改查等方法,只定义了一个特定的根据姓名查找人的方法,同时继承了一个JpaRepository接口。
2.无论继承接口也好,自定义方法也好,终究是接口,可是这里咱们连实现类也没有。
暂时先不走到业务逻辑Service层,一二三步走完,咱们这个持久层程序已经能够运行了,下面咱们编写测试方法来看看。
package com.test; import java.util.List; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.sdj.domain.Person; import com.sdj.repository.PersonRepository; public class TestSDJ { @Test public void testDB() { @SuppressWarnings("resource") ApplicationContext context = new ClassPathXmlApplicationContext("application-root.xml"); PersonRepository bean = (PersonRepository) context.getBean("personRepository"); List<Person> list = bean.findAll(); System.out.println(list); } }
运行测试类能够看到控制台输出以下结果
首先是格式化打印出了SQL语句,能够清楚看出来是查询数据表全部记录,下面则是输出集合内容,这样一来咱们成功查出了表中数据。
疑问点以下:
1.首先前面咱们定义PersonRepository是一个接口,而且没有实现类,更没有bean注解,那么经过Spring咱们为何能拿到这样一个(接口名首字母小写)名字的bean,这个bean又具体是什么?
2.咱们的PersonRepository是一个接口,明没有这样的findAll()方法来查询表中全部记录,有人可能会很快想到其继承了CrudRepository接口,那么这个方法又是怎么实现的?
JpaRepository这个接口是Spring Data提供的核心接口系列继承链中的一环,主要有以下四个
Repository
public interface Repository<T, ID extends Serializable> { }
这是顶层接口,也是一个空接口,后面定义的泛型T对应咱们的实体类,ID对应实体类中主键字段对应属性的类型,好比本文是数字主键类型Integer,这里即对应Integer。
@NoRepositoryBean public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> { <S extends T> S save(S entity); T findOne(ID id); Iterable<T> findAll(); ... }
CrudRepository继承Repository接口,CRUD你们应该都不陌生,增长(Create)、读取查询(Read)、更新(Update)和删除(Delete),这里即新增了增删改查等通用方法的定义。
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> { ... }
public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> { ...... }
而后PagingAndSortingRepository继承CrudRepository,同时新增分页和排序等相关方法
最后就是文中的JpaRepository继承PagingAndSortingRepository,同时定义了系列经常使用方法。
不知不觉,咱们能够看到JpaRepository这里已经基本涵盖了咱们基础操做的相关方法集合了,例如测试类中的查找全部记录方法。可是问题没完,方法再多,终究是接口,既然是接口,你这些方法没有实现的话咱们仍是没法使用,可是咱们在测试中已经发现成功了,那么它是怎么实现的呢。
咱们来关注一下JpaRepository的实现类SimpleJpaRepository,源代码类声明段落以下
@Repository @Transactional(readOnly = true) public class SimpleJpaRepository<T, ID extends Serializable> implements JpaRepository<T, ID>, JpaSpecificationExecutor<T> { ...... }
能够发现,这个类中已经实现了前面四环的定义方法,终于齐集五环。
@Repository,Spring系列bean注解之一,告诉系统这是一个持久层的bean示例;
@NoRepositoryBean,与之相反,使用该注解标明,此接口不是一个Repository Bean,例如前面的JpaRepository等,都用上了该注解,可是咱们自定义的PersonRepository并无加,同时前面配置文件中 <jpa:repositories base-package="com.sdj.repository"/> ,随后Spring Data会自动帮咱们实现该接口,并实例出bean,bean名字为接口名首字母小写后的字符串。
下面咱们在原先的测试类中加入以下代码
ApplicationContext context = new ClassPathXmlApplicationContext("application-root.xml"); String[] beanNames = context.getBeanDefinitionNames(); for(String beanName:beanNames) { System.out.println(beanName); } ...
从新运行测试类,咱们除了能看到先前的输出信息,在前面还会看到以下输出
这一行行的都是Spring容器中现有的bean示例名称,其中就有刚刚说到的"personRepository",因此咱们才能根据这个名称拿到该bean示例。
而后咱们在Service层就能够注入持久层bean去组合业务逻辑操做了,经过@Autowired注入,同时不要忘记Service类上的@Service注解。
@Service public class PersonServiceImpl implements PersonService { @Autowired PersonRepository personRepository; ..... }
这样一来,咱们发如今常规的基础操做范围内,包括增删改查、分页查询、排序等等,咱们不用编写一个方法,也不用写一条SQL语句,Spring Data Jpa都帮咱们封装好了。但这只是一部份内容,例如前面接口中咱们不是定义了一个findByName(),有人可能会说了,难不成这也能帮咱们自动实现?就算能,那我再findByGenger()?到底能不能,这也引出了下面要说的内容。
在前面的测试类查询方法改为以下:
List<Person> list = bean.findByName("小明");
运行测试方法,控制台输出以下
看SQL语句发现的确是根据name姓名去查的,也成功查到告终果。
你们都知道增删改查,一个查字一片天,简单查也好,花样查也好,它都是咱们持久层不可缺乏的部分。
除了前面提到了Spring Data对常规持久层操做的封装,它另外还提供了一种经过格式化命名建立查询的途径,使得咱们在建立查询方法的时候有更多简单的实现方式和选择。
这里的格式具体体现示例以下:
查询方法命名都是findBy****这样的开头,后面跟字段或者字段加关键字的组合
好比findByName等,至关于SQL:where name= ?都是规范的驼峰式命名。
好比findByNameAndGender,至关于SQL:where name= ? and gender = ?
这里的and就是一个keyword关键字,相似的还有许多,能够参考官方文档连接点击查看以下相关内容
也就是说符合上述命名规范的自定义方法,Spring Data一样会帮助咱们去实现这些方法,这样一来又方便了许多。
可是若是个人命名不符合规范,我是否必定要实现这个接口并重写相关方法,这样其实也是可行的,不过Spring Data还提供了@Query方法注解,供咱们自定义操做语句去实现它。例如上面的findByName方法,相似的咱们在接口中新建一个任意方法名的以下方法:
@Query("from Person p where p.name = (:name)")
List<Person> abc(@Param("name")String name);
而后在测试类中引用该方法,能实现与findByName相同的查询效果。
这里方法名给了个adc,同时方法上面注解定义了查询语句,用过Hibernate的HQL语句的应该比较熟悉,这不由让人想起,当初Hibernate用的人用起来都说好啊好啊,面向对象思惟啊,全自动啊,一句SQL都不用写啊,真牛逼啊!而后全是HQL.....
Spring Data还有不少特性,若是有兴趣也能够继续深刻学习一下。
目前来看,除了主流的MyBatis、Hibernate,Spring Data Jpa也有很多公司在使用,并且Spring Boot系列中基于Spring Data的数据访问也有使用到,毕竟Spring系列。我的比较喜欢Spring Data Jpa的点在数据库通用操做的封装,以及这些便利命名方法,这使得咱们在业务逻辑相对简单的状况下,能节省不少代码和时间。可是问题是咱们大多时候咱们要攻克去专一的每每是那些复杂的业务逻辑,而在这点上Spring Data Jpa并没有明显优点,莫非这就是在知乎上搜素"Spring Data怎么样"连话题都搜不出来的缘由....同时高封装会不会引起低可控,如同之前用Hibernate,它自动帮助咱们发送SQL,可是简便的同时不会有像MyBatis那样看到本身写SQL的透明度来得直观,同时SQL优化等东西彷佛没那么好掌控,这些应该是项目技术选型初始大体都会考虑到的一些问题吧,效率、性能、对开发人群的总体上手难度等等。总的来讲,根据应用场景作出最适合项目的选择才是关键吧。