spring data jpa 初实践 简单搭建spring+hibernate jpa

Java框架

浏览数:248

2020-6-8


关于spring data jpa

要了解spring data jpa 首先要了解什么是jpa

JPA全称Java Persistence API.JPA通过JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。

JPA 是 JCP 组织发布的 Java EE 标准之一,因此任何声称符合 JPA 标准的框架都遵循同样的架构,提供相同的访问API,这保证了基于JPA开发的企业应用能够经过少量的修改就能够在不同的JPA框架下运行。

简单点说jpa就是一套持久化框架的标准,实现jpa的标准来对数据库进行持久化操作,举几个符合jpa标准的例子:

Hibernate

JPA是需要Provider来实现其功能的,Hibernate就是JPA Provider中很强的一个,应该说无人能出其右。从功能上来说,JPA就是Hibernate功能的一个子集。Hibernate 从3.2开始,就开始兼容JPA。Hibernate3.2获得了Sun TCK的JPA(Java Persistence API) 兼容认证。

只要熟悉Hibernate或者其他ORM框架,在使用JPA时会发现其实非常容易上手。例如实体对象的状态,在Hibernate有自由、持久、游离三种,JPA里有new,managed,detached,removed,明眼人一看就知道,这些状态都是一一对应的。再如flush方法,都是对应的,而其他的再如说Query query = manager.createQuery(sql),它在Hibernate里写法上是session,而在JPA中变成了manager,所以从Hibernate到JPA的代价应该是非常小的

同样,JDO,也开始兼容JPA。在ORM的领域中,看来JPA已经是王道,规范就是规范。在各大厂商的支持下,JPA的使用开始变得广泛。

 

Spring

Spring + Hibernate 常常被称为 Java Web 应用人气最旺的框架组合。而在 JCP 通过的 Web Beans JSR ,却欲将JSF + EJB + JPA 、来自 JBoss Seam(Spring 除外)的一些组件和EJB 3(能够提供有基本拦截和依赖注入功能的简化 Session Bean框架)的一个 Web 组合进行标准化。Spring 2.0 为 JPA 提供了完整的 EJB容器契约,允许 JPA在任何环境内可以在 Spring 管理的服务层使用(包括 Spring 的所有DI 和 AOP增强)。同时,关于下一个Web应用组合会是 EJB、Spring + Hibernate 还是 Spring + JPA 的论战,早已充斥于耳。

在Spring 2.0.1中,正式提供对JPA的支持,这也促成了JPA的发展,要知道JPA的好处在于可以分离于容器运行,变得更加的简洁。

我们看到spring和hibernate都兼容了jpa标准,我今天实践的也是基于 Hibernate EntityManager 的开发,来实现一个spring mvc + spring+ hibernate jpa的简单框架。

 

环境搭建

 

1. maven

    spring 版本:4.3.3

    hibernate版本:4.3.6

    spring data jpa 版本1.10.5

 pom.xml 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>sqp.demo</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>1.0-SNAPSHOT</version>


    <properties>
        <springframework.version>4.3.3.RELEASE</springframework.version>
        <hibernate.version>4.3.6.Final</hibernate.version>
        <mysql.connector.version>5.1.31</mysql.connector.version>
    </properties>

    <dependencies>
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${springframework.version}</version>
        </dependency>

        <!-- spring data jpa -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>1.10.5.RELEASE</version>
        </dependency>


        <!-- Hibernate -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hibernate.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.hibernate</groupId>
                    <artifactId>ejb3-persistence</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.hibernate</groupId>
                    <artifactId>hibernate</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.hibernate</groupId>
                    <artifactId>hibernate-annotations</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- MySQL -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.connector.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.26</version>
        </dependency>


        <!-- Servlet+JSP+JSTL -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>javax.servlet.jsp-api</artifactId>
            <version>2.3.1</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

        <!-- Jackson -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.7.4</version>
        </dependency>

        <!-- log4j -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>


    </dependencies>

    <build>
        <finalName>ace-framework</finalName>

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
        <resources>
            <resource>
                <directory>src/main/java</directory>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
            </resource>
        </resources>
    </build>
</project>

 

2.spring jpa配置

spring + spring mvc的搭建暂不表,可以到本文下方查看源码.

spring mvc和spring的配置文件是分开的,我们这里需要修改的是spring的配置文件,添加spring data jpa的核心组件 Entity Manager

    <!-- 数据源 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.user}" />
        <property name="password" value="${jdbc.pass}" />
    </bean>


    <!-- EntityManager -->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="packagesToScan" value="sqp.demo.model" />
        <property name="persistenceProvider">
            <bean class="org.hibernate.jpa.HibernatePersistenceProvider" />
        </property>
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="generateDdl" value="${hibernate.generateDDL}" />
                <property name="database" value="${hibernate.database}" />
                <property name="databasePlatform" value="${hibernate.dialect}" />
                <property name="showSql" value="${hibernate.show_sql}" />
            </bean>
        </property>
        <property name="jpaPropertyMap">
            <map>
                <entry key="hibernate.query.substitutions" value="true 1, false 0" />
                <entry key="hibernate.default_batch_fetch_size" value="20" />
                <entry key="hibernate.max_fetch_depth" value="6" />
                <entry key="hibernate.generate_statistics" value="true" />
                <entry key="hibernate.bytecode.use_reflection_optimizer" value="true" />
                <entry key="hibernate.cache.use_second_level_cache" value="false" />
                <entry key="hibernate.cache.use_query_cache" value="false" />
            </map>
        </property>
        <property name="jpaDialect">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
        </property>
    </bean>

 

注:几个动态参数的解释

    generateDdl 自动建表配置,功能类似于hibernate的hbm2ddl

    database 数据库连接

    databasePlatform 数据库dialect

    showSql 操作数据库是是否打印sql语句

为spring添加jpa接口的扫描

    <!-- 启用jpa扫描-->
    <jpa:repositories base-package="sqp.demo.dao" entity-manager-factory-ref="entityManagerFactory"
            transaction-manager-ref="transactionManager" repository-impl-postfix="Impl"/>

   

注:repository-impl-postfix 即自定义repository实现的时候寻类后缀,后面会提到。

spring data jpa 的事务配置

    <!-- 配置Hibernate事务管理器 -->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>
    <!-- 配置注解式事务 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

注:我们这里依然是才用了注解式事务的实现,相信这也是普遍的做法了

3. JPA部分代码

简单创建一个实体类先

@Entity
@Table(name = "student")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    private String name;

    private Integer age;

    //节省篇幅getter and setter略...
}

创建Repository

@RepositoryDefinition(domainClass = Student.class,idClass = Long.class)
public interface StudentRepository extends CrudRepository<Student, Long>{
    
}

这里Repository后缀相当于常用的Dao后缀,不过既然官方用Repository,为了保持一致性,建议也用Repository做后缀。

注:

1. 本类是接口

2. 接口CrudRepository是spring data jpa提供的crud默认接口,接口中定义了一些常用的crud操作,具体可以看源码,类似的接口还有PagingAndSortingRepository等。

service 和 controller

@Service
public class StudService {
    @Autowired
    private StudentRepository studentRepository;

    @Transactional(propagation = Propagation.REQUIRED)
    public void save(Student student){
        studentRepository.save(student);
    }
}
@Controller
public class IndexController {
    @Autowired
    private StudService studService;

    @RequestMapping("/index")
    @ResponseBody
    public String hello(){
        Student student = new Student();
        student.setName("test00000");
        student.setAge(8);
        studService.save(student);
        return "hello"+ student.getId();
    }
}

启动项目,访问localhsot:8080/index 页面显示hello 1,说明程序是跑成功了的。

在service中直接调用了repository的save方法,这个方法是crudRepository接口提供的。

为什么crudRepository没有实现类却可以实现保存的功能呢?

这就是spring data jpa最吸引开发者功能- 通过解析方法名创建查询

框架在进行方法名解析时,会先把方法名多余的前缀截取掉,比如 find、findBy、read、readBy、get、getBy,然后对剩下部分进行解析。并且如果方法的最后一个参数是 Sort 或者 Pageable 类型,也会提取相关的信息,以便按规则进行排序或者分页查询。

在创建查询时,我们通过在方法名中使用属性名称来表达,比如 findByUserAddressZip ()。框架在解析该方法时,首先剔除 findBy,然后对剩下的属性进行解析,详细规则如下(此处假设该方法针对的域对象为 AccountInfo 类型):

  • 先判断 userAddressZip (根据 POJO 规范,首字母变为小写,下同)是否为 AccountInfo 的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,继续第二步;
  • 从右往左截取第一个大写字母开头的字符串(此处为 Zip),然后检查剩下的字符串是否为 AccountInfo 的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,则重复第二步,继续从右往左截取;最后假设 user 为 AccountInfo 的一个属性;
  • 接着处理剩下部分( AddressZip ),先判断 user 所对应的类型是否有 addressZip 属性,如果有,则表示该方法最终是根据 “AccountInfo.user.addressZip” 的取值进行查询;否则继续按照步骤 2 的规则从右往左截取,最终表示根据 “AccountInfo.user.address.zip” 的值进行查询。

可能会存在一种特殊情况,比如 AccountInfo 包含一个 user 的属性,也有一个 userAddress 属性,此时会存在混淆。读者可以明确在属性之间加上 “_” 以显式表达意图,比如 “findByUser_AddressZip()” 或者 “findByUserAddress_Zip()”。

在查询时,通常需要同时根据多个属性进行查询,且查询的条件也格式各样(大于某个值、在某个范围等等),Spring Data JPA 为此提供了一些表达条件查询的关键字,大致如下:

  • And — 等价于 SQL 中的 and 关键字,比如 findByUsernameAndPassword(String user, Striang pwd);
  • Or — 等价于 SQL 中的 or 关键字,比如 findByUsernameOrAddress(String user, String addr);
  • Between — 等价于 SQL 中的 between 关键字,比如 findBySalaryBetween(int max, int min);
  • LessThan — 等价于 SQL 中的 “<“,比如 findBySalaryLessThan(int max);
  • GreaterThan — 等价于 SQL 中的”>”,比如 findBySalaryGreaterThan(int min);
  • IsNull — 等价于 SQL 中的 “is null”,比如 findByUsernameIsNull();
  • IsNotNull — 等价于 SQL 中的 “is not null”,比如 findByUsernameIsNotNull();
  • NotNull — 与 IsNotNull 等价;
  • Like — 等价于 SQL 中的 “like”,比如 findByUsernameLike(String user);
  • NotLike — 等价于 SQL 中的 “not like”,比如 findByUsernameNotLike(String user);
  • OrderBy — 等价于 SQL 中的 “order by”,比如 findByUsernameOrderBySalaryAsc(String user);
  • Not — 等价于 SQL 中的 “! =”,比如 findByUsernameNot(String user);
  • In — 等价于 SQL 中的 “in”,比如 findByUsernameIn(Collection<String> userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;
  • NotIn — 等价于 SQL 中的 “not in”,比如 findByUsernameNotIn(Collection<String> userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;

 

这时候你也许会想到一个问题, 如果我们需要的查询太复杂方法名不能提供我们想要的查询怎么办?

spring data jpa还为我们提供一种自定义实现的方式。前文spring配置jpa扫描中有repository-impl-postfix这一参数,含义就是“自定义实现类的后缀”,spring data jpa 通过这一后缀寻找自定义实现。比如StudentRepository的自定义实现类为StudentRepositoryImpl。

@RepositoryDefinition(domainClass = Student.class,idClass = Long.class)
public interface StudentRepository extends CrudRepository<Student, Long>,PagingAndSortingRepository<Student,Long>{
    void sayHello();
}
@Repository
public class StudentRepositoryImpl{
    @PersistenceContext
    private EntityManager entityManager;

    public void sayHello() {
        System.out.println("hello world");
    }
}

注:

1. StudentRepositoryImpl并不需要实现StudentRepository接口。

2. StudentRepository 中需要定义与Impl相同方法名的方法。

 

文中示例源码:http://git.oschina.net/sqp/spring-data-jpa 

未完待续…

 

 

作者:蓝薯