欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring回顾之六 —— JPA,另一种数据方案的尝试

程序员文章站 2022-07-12 18:31:39
...
    JPA,即Java持久性API(Java Persistence API),是JavaEE5发布的一个ORM规范。JPA致力于为Java开发人员提供对象/关系映射工具的规范,而这种工具可以帮助管理程序中的数据,能简化开发流程,让开发者专注于具体的业务逻辑上,故而备受欢迎。现在各大ORM框架的热捧之下,Spring也做出相应的欢迎姿态,已经提供了多种JPA实现的集成方案,包括Hibernate、OpenJPA、EclipseJPA等。SpringDataJPA的使用能进一步简化数据访问层和持久层功能的管理和创建,可以让程序开发过程中更多的精力放在业务的设计实现上,接下来我们看看如何在之前的项目中,使用JPA。

第一步:引入依赖资源
    在JPA面前,MyBatis这种以轻悍著称的ORM就处于比较尴尬境地的,由于Spring的搭桥,JPA的在实际开发中也同样具备灵活便捷的特点,成为当下一种不错的选择方案。考虑到市面的流行程度,我们选择集成Hibernate的JPA实现来做一下尝试,接下来先引入所需的依赖jar包,打开pom文件,在dependencies里添加以下代码
		<!-- ============== jpa begin ============== -->
		<dependency><!-- hibernate 核心工具包  -->
		    <groupId>org.hibernate</groupId>
		    <artifactId>hibernate-core</artifactId>
		    <version>5.2.8.Final</version>
		</dependency>		
		<dependency><!-- hibernate 对象管理工具包  -->
		    <groupId>org.hibernate</groupId>
		    <artifactId>hibernate-entitymanager</artifactId>
		    <version>5.2.8.Final</version>
		</dependency>
		<dependency><!-- Java持久化API,提供了一种对象/关系映射工具 -->
		    <groupId>org.springframework.data</groupId>
		    <artifactId>spring-data-jpa</artifactId>
		    <version>1.11.1.RELEASE</version>
		</dependency>
		<!-- ============== jpa end ============== -->

    这里显式声明的是主要的三个依赖包,maven会根据依赖关系,在更新项目时同时引入他们所依赖的一系列基础的工具包,有兴趣的可以看下pom文件的依赖树
      Spring回顾之六 —— JPA,另一种数据方案的尝试
            
    
    博客分类: SpringJavaJPA jpaspringhibernatejavaSpringData

    接下来我们看看如何在Spring容器中使用JPA。

第二步:整合装配
    这次对JPA的尝试,我们准备直接在单元测试里验证,就不去在web.xml做相应配置了,所以我们直接去resource文件夹下创建一个名为applicationContext-JPA.xml的文件,文件代码如下
<?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:context="http://www.springframework.org/schema/context"
    xmlns:p="http://www.springframework.org/schema/p" 
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd 
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd 
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
    http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd ">

    <!-- 加载配置文件 -->
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:jdbc.properties" />
    </bean>
    <!-- ========================= JPA BEGIN  ========================= -->
    <!-- 数据源配置 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <!-- 初始化连接大小 -->
        <property name="initialSize" value="${jdbc.initialSize}"></property>
        <!-- 连接池最大数量 -->
        <property name="maxActive" value="${jdbc.maxActive}"></property>
        <!-- 连接池最大空闲 -->
        <property name="maxIdle" value="${jdbc.maxIdle}"></property>
        <!-- 连接池最小空闲 -->
        <property name="minIdle" value="${jdbc.minIdle}"></property>
        <!-- 获取连接最大等待时间 -->
        <property name="maxWait" value="${jdbc.maxWait}"></property>
    </bean>
    
    <!-- JPA实体管理器工厂 适用于所有环境的FactoryBean,控制EntityManagerFactory配置,如指定Spring定义的DataSource等,完全掌管JPA -->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <!-- 指定定义的数据源 -->
        <property name="dataSource" ref="dataSource"/>
        <!-- 指定Entity实体类包路径 -->
        <property name="packagesToScan">
            <list>
                <value>test.demo.jpa.entity</value>
            </list>
        </property>
        <!-- 设置实现厂商JPA实现的特定属性,指定是否显示SQL的是否显示、方言等 -->
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <!-- 是否自动生成DDL的属性generateDdl -->
                <property name="generateDdl" value="false"/>
                <!-- 是否展示sql -->
                <property name="showSql" value="false"/>
                <!-- 对应数据库使用的方言 -->
                <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"/>
                <!-- 指定使用的数据库类型(目前支持DB2、DERBY、H2、HSQL、INFORMIX、MYSQL、ORACLE、POSTGRESQL、SQL_SERVER、SYBASE)-->
                <property name="database" value="MYSQL"/>
            </bean>
        </property>
        <!-- 指定JPA属性;如Hibernate中指定是否显示SQL的是否显示、方言等 -->
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop><!-- 制定方言,对应数据库 -->
                <prop key="hibernate.show_sql">false</prop><!-- 日志中是否显示SQL语句  -->
                <prop key="hibernate.format_sql">true</prop><!-- 日志中显示SQL语句是否美化格式化  -->
                <prop key="hibernate.hbm2ddl.auto">update</prop><!-- hbm2ddl的设置是控制在项目启动的过程中,自动检查注解的实体和数据表,如果数据库不存在的标,会根据实体自动生成 -->
                <prop key="hibernate.enable_lazy_load_no_trans">true</prop>
            </props>
        </property>
    </bean>
    <!-- Jpa事务管理器  -->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>
    <!-- 重要配置:启用扫描并自动创建代理的功能  -->
    <jpa:repositories base-package="test.demo.jpa.dao" transaction-manager-ref="transactionManager" entity-manager-factory-ref="entityManagerFactory"/>
    <!-- ========================= JPA END  ========================= -->
</beans> 

    配置文件的开始是加载数据库连接信息的配置文件,紧接着就是数据源的配置,和MyBatis等使用时都是一样的,这个不多说。
    接下来我们要重点关注JPA实体管理器工厂的配置,Spring是通过EntityManagerFactory来实现集成JPA的。这里要提下,LocalEntityManagerFactoryBean是用于那些仅使用JPA进行数据访问的项目,该Bean将根据JPAPersistenceProvider的设置去自动检测加载配置文件,这种方式简单是简单,但是不支持在Spring中配置DataSource,同时也不支持Spring管理的全局事务。然而LocalContainerEntityManagerFactoryBean是号称适用于所有环境的实体管理器工厂,可以和Spring容器完美结合,我们这里要用的就是这个LocalContainerEntityManagerFactoryBean。Spring通过对EntityManager的创建销毁的其统一管理,将事务等抽取出来,让开发过程中的更多精力用于具体业务的实现。
    紧接着就是指定数据源,这个很重要,但也没啥好说的,注意数据源名字别搞错就行了。然后配置扫描的Entity实体类的包路径,这个路径可以是定义多个。接下来配置的这个jpaVendorAdapter,是用来设置实现厂商JPA实现的特定属性,像generateDdl属性设置Hibernate的是否自动生成DDL,showSql属性决定执行时是否打印SQL语句,其中比较重要的是要注意databasePlatform属性,这个是用来设置对应数据库的方言的,方言的设置获取连接对象的事务等高级功能,这个不设置会默认匹配相应的实现,但一定不能写错。jpaProperties也是做JPA属性配置的一种方式,如果jpaVendorAdapter中没有配置的,可以在这里进行设置,如果都做了设置加载时会进行属性覆盖,容器会以这里设定的值为准。
    紧接着的这个transactionManager是用来做事物管理的,注意这里将使用jpa专属的JpaTransactionManager事务管理器。最后还要配置下事务管理器和实体管理器工厂要扫描并自动代理的路径,这样一来,我们就可以直接在容器中获取所需的的资源。
    看完了配置文件的装配过程,接下来我们看看JAP在代码中怎么使用。


第三步:使用和单元测试
    我们在这里将用单元测试的方式进行验证JPA的使用,先在test.demo.jpa.entity的包路径下创建一个名为UserEntity的实体类,代码如下
package test.demo.jpa.entity;

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="d_user")
public class UserEntity {
	@Id
	@GeneratedValue(strategy=GenerationType.AUTO)
	@Column(name="u_id")
	private Integer id;
	@Column(name="u_name")
	private String 	name;
	@Column(name="u_password")
	private String 	password;
	@Column(name="u_age")
	private Integer age;
	
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
}

    这里除了不能忘记写@Entity标签,还要注意@Table标签,name的值对应的是数据库中实际的表名,以及@Column标签name的值对应的是表中的字段名称,这个不能搞错。写好了实体类,我们还要写一个作具体操作的接口,紧接着在test.demo.jpa.dao路径下创建一个名为UserJPADAO的接口,这个要继承SpringDataJPA提供的JpaRepository接口,代码如下
package test.demo.jpa.dao;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import test.demo.jpa.entity.UserEntity;

public interface UserJPADAO extends JpaRepository<UserEntity, Long>{
	
	public UserEntity getUserById(@Param("userId") Integer userId);
	
	//@Query(value="select u from UserEntity u where u.id = :userId")
	@Query("select u from UserEntity u where u.id = :userId")
	public UserEntity getUserByIdUsedHql(@Param("userId") Integer userId);

	@Query(value="select u.u_id, u.u_name, u.u_password, u.u_age from d_user u WHERE u.u_id = :userId", nativeQuery = true)
	public UserEntity getUserByIdUsedNative(@Param("userId") Integer userId);
}

    先看SpringDataJPA提供的这个JpaRepository接口,继承的PagingAndSortingRepository接口,是在父接口的基础上,又提供了一些比较实用的方法,诸如flush()、saveAndFlush()、deleteInBatch()等实用方法。
    接着我们来看这三个功能相同的接口方法:getUserById我们直接定义了一个接口,给个参数,不做其他内容的添加;getUserByIdUsedHql我们通过@Query标签用HQL方式进行查询,HQL是一种丰富灵活接近于原生SQL的语言,这个感兴趣可以专门去看下Hibernate的那块儿内容;getUserByIdUsedNative我们直接采用了原生SQL的查询方式,这里注意,@Query标签的nativeQuery值一定要设置成true,表示这个接口将使用原生SQL的形式来解析语句进行查询。
    写好之后我们就去写个测试案例,看看这几个定义好的接口是否都能如期实现功能。我们在测试模块里test.demo包路径下,创建名为SpringTestUserForJPA的测试类,实现代码如下

package test.demo;

import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import test.demo.jpa.dao.UserJPADAO;
import test.demo.jpa.entity.UserEntity;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath*:applicationContext-JPA.xml"})
public class SpringTestUserForJPA {
	private static final Logger logger = LoggerFactory.getLogger(SpringTestUserForJPA.class);
	
	@Resource
	private UserJPADAO userDao;
	
	@Test
	public void testGetUserById() {
		Integer userId = 1;
		UserEntity user1 = userDao.getUserById(userId);
		logger.info("getUserById -- 用户的名字是 : " + user1.getName());
		userId = 2;
		UserEntity user2 = userDao.getUserByIdUsedHql(userId);
		logger.info("getUserByIdUsedHql -- 用户的名字是 : " + user2.getName());
		UserEntity user3 = userDao.getUserByIdUsedNative(userId);
		logger.info("getUserByIdUsedNative -- 用户的名字是 : " + user3.getName());
	}
}

    我们去配置文件里将那个设置是否显示sql打印的属性设置成false,方便日志的查看,然后行测试....接下来成功后我们可以在console栏看到如下日志信息
      Spring回顾之六 —— JPA,另一种数据方案的尝试
            
    
    博客分类: SpringJavaJPA jpaspringhibernatejavaSpringData

    哎哎哎~这个太神奇了,后边这俩方法还好理解,但是是第一个getUserById方法,我们几乎什么都没做,简直太爽了,什么情况?其实DAO继承的JpaRepository,已经帮我们做了好多事情,接下来专门看下JAP一些比较实用的使用重点。

第四步:JPA小计
    JPA众多知识点这里不做赘述,想重点说一说SpringDataJPA的Repository接口和支持方法名解析查询特性。
    先说Repository,本身是一个泛型接口,需要为其提供两个类型:第一个是该接口要处理的域对象类型,第二个是该域对象的主键类型,它本身不包含任何方法。SpringDataJPA为其准备了众多子接口,分别实现了一些常用的增删改查,以及分页等方法。我们DAO继承的JpaRepository接口,就是继承自PagingAndSortingRepository->CrudRepository->Repository的,这些子类提供了丰富的具体实现,如果我们不想暴露给业务过多底层方法,直接继承Repository,只定义自己的接口,也是可以的。
    除了Repository提供的基础操作,SpringDataJPA还定义了一套用来自定义的查询方式,其中一个功能就是在后台为持久层接口创建代理对象时,会解析方法名字,并实现其相应的功能。除此之外,也接受@Query标签来通过查询语句进行操作,这个上边的测试已经看到效果。
    这里看下这个解析方法名字的规则,SpringDataJPA会在在进行方法名解析时,会先把方法名类似于find、findBy、read、readBy、get、getBy的前缀截掉,然后对剩下部分进行解析。并且如果方法的最后一个参数是Sort或者Pageable类型,也会根据相关的信息做排序或者分页查询。SpringDataJPA提供的一些方法名表达条件查询的关键字,大致如下

    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	-- 等价于SQL中的 "is not null",等价于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 类型,也可以是数组或者不定长参数;

    如果想在使用中享受这种便利,就要严格按照这个规则来命名接口方法。
除此之外,SpringDataJPA还有很多便利的特点,其他的以后有空专门整理,感兴趣可以去查查资料深入了解。








  • Spring回顾之六 —— JPA,另一种数据方案的尝试
            
    
    博客分类: SpringJavaJPA jpaspringhibernatejavaSpringData
  • 大小: 2.1 KB
  • Spring回顾之六 —— JPA,另一种数据方案的尝试
            
    
    博客分类: SpringJavaJPA jpaspringhibernatejavaSpringData
  • 大小: 55.1 KB