实例详解Spring Data JPA的使用

什么是Spring Data JPA

​ Spring Data 项目的目的是为了简化构建基于 Spring 框架应用的数据访问计数,包括非关系数据库、Map-Reduce 框架、云数据服务等等;另外也包含对关系数据库的访问支持。

这里引用一下博客园 神一样存在 的一篇博客的内容

(原文:http://www.cnblogs.com/dreamroute/p/5173896.htm )

​ JPA诞生的缘由是为了整合第三方ORM框架,建立一种标准的方式,百度百科说是JDK为了实现 ORM的天下归一,目前也是在按照这个方向发展,但是还没能完全实现。在ORM框架中,Hibernate是一支很大的部队,使用很广泛,也很方便,能力 也很强,同时Hibernate也是和JPA整合的比较良好,我们可以认为JPA是标准,事实上也是,JPA几乎都是接口,实现都是Hibernate在 做,宏观上面看,在JPA的统一之下Hibernate很良好的运行。

​ 上面阐述了JPA和Hibernate的关系,那么Spring-data-jpa又是个什么东西呢?这地方需要稍微解释一下,我们做Java开 发的都知道Spring的强大,到目前为止,企业级应用Spring几乎是无所不能,无所不在,已经是事实上的标准了,企业级应用不使用Spring的几 乎没有,这样说没错吧。而Spring整合第三方框架的能力又很强,他要做的不仅仅是个最早的IOC容器这么简单一回事,现在Spring涉及的方面太 广,主要是体现在和第三方工具的整合上。而在与第三方整合这方面,Spring做了持久化这一块的工作,我个人的感觉是Spring希望把持久化这块内容 也拿下。于是就有了Spring-data-**这一系列包。包括,Spring-data-jpa,Spring-data- template,Spring-data-mongodb,Spring-data-redis,还有个民间产品,mybatis-spring,和前 面类似,这是和mybatis整合的第三方包,这些都是干的持久化工具干的事儿。

这里介绍Spring-data-jpa,表示与jpa的整合。

​ 我们都知道,在使用持久化工具的时候,一般都有一个对象来操作数据库,在原生的Hibernate中叫做Session,在JPA中叫做 EntityManager,在MyBatis中叫做SqlSession,通过这个对象来操作数据库。我们一般按照三层结构来看的话,Service层 做业务逻辑处理,Dao层和数据库打交道,在Dao中,就存在着上面的对象。那么ORM框架本身提供的功能有什么呢?答案是基本的CRUD,所有的基础 CRUD框架都提供,我们使用起来感觉很方便,很给力,业务逻辑层面的处理ORM是没有提供的,如果使用原生的框架,业务逻辑代码我们一般会自定义,会自 己去写SQL语句,然后执行。在这个时候,Spring-data-jpa的威力就体现出来了,ORM提供的能力他都提供,ORM框架没有提供的业务逻辑 功能Spring-data-jpa也提供,全方位的解决用户的需求。使用Spring-data-jpa进行开发的过程中,常用的功能,我们几乎不需要 写一条sql语句,至少在我看来,企业级应用基本上可以不用写任何一条sql,当然spring-data-jpa也提供自己写sql的方式,这个就看个 人怎么选择,都可以。我觉得都行。

Spring Data JPA使用示例

创建实体例

@Entity
@Table(name = "T_COURIER")
public class Courier {

    @Id
    @GeneratedValue
    @Column(name = "C_ID")
    private Integer id;
    @Column(name = "C_COURIER_NUM", unique = true)
    private String courierNum; 
    @Column(name = "C_NAME")
    private String name; 

    @ManyToOne
    @JoinColumn(name = "C_STANDARD_ID")
    private Standard standard;
@Entity
@Table(name = "T_STANDARD")
public class Standard {
    @Id
    @GeneratedValue
    @Column(name = "C_ID")
    private Integer id;
    @Column(name = "C_NAME")
    private String name; 
    @Column(name = "C_OPERATOR")
    private String operator; 
    @Column(name = "C_OPERATING_COMPANY")
    private String operatingCompany;

Spring Data JPA

  1. 在spring的applicationContext.xml引入spring data jpa名称空间

    
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc" 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.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/data/jpa 
        http://www.springframework.org/schema/data/jpa/spring-jpa.xsd ">
  2. 配置entityManagerFactory ,JPA编程核心接口EntityManager (类似Session,进行增删改查 )

    <bean id="entityManagerFactory"
        class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="packagesToScan" value="cn.roger.domain" />
        <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="true" />
                <property name="database" value="ORACLE" />
                <property name="databasePlatform" value="org.hibernate.dialect.Oracle10gDialect" />
                <property name="showSql" value="true" />
            bean>
        property>
        <property name="jpaDialect">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
        property>
        <property name="jpaPropertyMap">
            <map>
                <entry key="hibernate.query.substitutions" value="true 1, false 0" />
                <entry key="hibernate.default_batch_fetch_size" value="16" />
                <entry key="hibernate.max_fetch_depth" value="2" />
                <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>
    bean>
  3. 基于jpa进行事务管理

    
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    bean>
    
    
    <tx:annotation-driven transaction-manager="transactionManager"/>
  4. 扫描DAO接口,自动生成实现类

    <jpa:repositories base-package="cn.roger.dao" />

完整的xml配置:


<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" 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.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
    http://www.springframework.org/schema/data/jpa 
    http://www.springframework.org/schema/data/jpa/spring-jpa.xsd ">


<context:component-scan base-package="cn.roger"/>


<context:property-placeholder location="classpath:config.properties" />


<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="${jdbc.driver}" />
    <property name="jdbcUrl" value="${jdbc.url}" />
    <property name="user" value="${jdbc.user}" />
    <property name="password" value="${jdbc.password}" />
bean>


<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="packagesToScan" value="cn.roger.domain" />
    <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="true" />
            <property name="database" value="ORACLE" />
            <property name="databasePlatform" value="org.hibernate.dialect.Oracle10gDialect" />
            <property name="showSql" value="true" />
        bean>
    property>
    <property name="jpaDialect">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
    property>
    <property name="jpaPropertyMap">
        <map>
            <entry key="hibernate.query.substitutions" value="true 1, false 0" />
            <entry key="hibernate.default_batch_fetch_size" value="16" />
            <entry key="hibernate.max_fetch_depth" value="2" />
            <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>
bean>


<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
bean>


<tx:annotation-driven transaction-manager="transactionManager"/>


<jpa:repositories base-package="cn.roger.dao" />

beans>

DAO接口编写规则

​ DAO的接口:

  • Repository (空接口)

  • CrudRepository (增删改查)

  • PagingAndSortingRepository (分页和排序)

  • JpaRepository (扩展增删改查、批量操作)

  • JpaSpecificationExecutor:用来做负责查询的接口

  • Specification:是Spring Data JPA提供的一个查询规范,要做复杂的查询,类似hibernate QBC查询

    JpaRepository继承PagingAndSortingRepository,CrudRepository:

    • CrudRepository 提供 save、 delete、 deteleAll、 findAll、 findOne、 count等方法

    • PagingAndSortingRepository提供排序及分页findAll(Sort) (基于排序的查询)、findAll(Pageable)( 基于分页的查询)

    所以继承JpaRepository 就拥有父类的方法,而父类具体实现(simpleJpaRepository)由spring 容器来调用(动态代理)ORM框架来完成ORID的操作。

//JpaRepository第一个参数是实体类,第二个参数是实体的主键类型
public interface CourierRepository extends JpaRepository<Courier, Integer>,
    JpaSpecificationExecutor<Courier> {
}
public interface StandardRepository extends JpaRepository<Standard, Integer> {
}

查询方法的命名规则与JPQL语法

Keyword Sample JPQL snippet
And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
Is,Equals findByFirstname,findByFirstnameIs
Between findByStartDateBetween … where x.startDatebetween 1? and ?2
LessThan findByAgeLessThan … where x.age < ?1 LessThanEqual findByAgeLessThanEqual
GreaterThan findByAgeGreaterThan … where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
After findByStartDateAfter … where x.startDate > ? 1
Before findByStartDateBefore … where x.startDate < ? 1
IsNull findByAgeIsNull … where x.age is null
IsNotNull,NotNull findByAge(Is)NotNull … where x.age not null
Like findByFirstnameLike … where x.firstname like ?1
NotLike findByFirstnameNotLike … where x.firstname not like ?1
StartingWith findByFirstnameStartingWith … where x.firstname like ?1 (parameter bound with appended %)
EndingWith findByFirstnameEndingWith … where x.firstname like ?1 (parameter bound with prepended %)
Containing findByFirstnameContaining … where x.firstname like ?1 (parameter bound wrapped in %)
OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
Not findByLastnameNot … where x.lastname <> ? 1
In findByAgeIn(Collection ages) … where x.age in ?1
NotIn findByAgeNotIn(Collection age) … where x.age not in ?1
True findByActiveTrue() … where x.active = true
False findByActiveFalse() … where x.active = false
IgnoreCase findByFirstnameIgnoreCase … where UPPER(x.firstame) = UPPER(?1)

查询方式

Spring data Query 使用实现条件查询JPA 提供了以下几种查询方式:

  1. 根据方法命名规则自动生成:

    • 基于一列等值查询 findBy属性名 例如:findByName(String name)

    • 基于一列模糊查询findBy属性名Like 例如:findByNameLike(String name)

    • 基于两列等值查询findBy属性名And属性名例如:findByUsernameAndPassword(String username,String password)

    public interface StandardRepository extends JpaRepository<Standard, Integer> {
    
        //基于一列等值查询 findBy属性名
        public List findByName(String name);
    
        //基于一列模糊查询findBy属性名Like
        public List findByNameLike(String name);
    
        //基于两列等值查询findBy属性名And属性名
        public List findByUsernameAndPassword(String username,String password);
    
    }
  2. 不按命名规则写的查询方法,可以配置@Query绑定JPQL语句或者SQL语句

    public interface StandardRepository extends JpaRepository<Standard, Integer> {
    
    @Query(value="from Standard where name = ?" ,nativeQuery=false)
    // nativeQuery 为 false 配置JPQL 、 为true 配置SQL 
    public List queryName(String name);
    
    @Query(value="update Standard set minLength=?2 where id =?1")
    @Modifying
        //带有条件修改和删除操作需要Modifying注解
    public void updateMinLength(Integer id , Integer minLength);
    }

    ​ nativeQuery 为 false 配置JPQL 、 为true 配置SQL .

    ​ 带有条件修改和删除操作需要Modifying注解。

    注意:方法参数位置顺序,如果在JPQL语句中参数没有标明位置,那么需要方法参数对调。

  3. 不按命名规则写的查询方法,配置@Query,不用写JPQL语句

    public interface StandardRepository extends JpaRepository<Standard, Integer> {
    
    @Query
    public List queryName(String name);
    
    }

    需要在实体类上写@NamedQueries,这种会带来代码之间的耦合,一般不会使用。

    @Entity
    @Table(name = "T_STANDARD")
    @NamedQueries({
    @NamedQuery(name="Standard.queryName2",query="from Standard where name=?")})
    public class Standard {
  4. JPA的Criteria查询

    接口只需要继承JpaSpecificationExecutor

    public interface CourierRepository extends JpaRepository<Courier, Integer>,
        JpaSpecificationExecutor<Courier> {
    }

    条件查询的方法:

    List findAll(Specificationspec);

    测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class StandardRepositoryTest {
    @Autowired
    private StandardRepository standardRepository; 

    @Test
    public void testQuery(){
        System.out.println(standardRepository.queryName("Job"));
    }

    @Test
    @Transactional
    @Rollback(false)
    public void testUpdate(){
        standardRepository.updateName("Job", "Roger");
    }
}

JPA的Criteria查询示例

public class CourierAction extends ActionSupport implements
        ModelDriven {

    // 模型驱动
    private Courier courier = new Courier();

    @Override
    public Courier getModel() {
        return courier;
    }

    // 注入Service
    @Autowired
    private CourierService courierService;

    // 增加的方法
    public String save() {
        courierService.save(courier);
        return SUCCESS;
    }

    // 属性驱动接收客户端分页参数
        //分页查询需要page与rows两个属性
    private int page;
    private int rows;

    public void setPage(int page) {
        this.page = page;
    }

    public void setRows(int rows) {
        this.rows = rows;
    }

    // 分页查询方法
    public String pageQuery() {
        // 封装Pageable对象
            //page一般是从1开始的,分页查询是从0开始的,所以需要减1
        Pageable pageable = new PageRequest(page - 1, rows);
        // 封装条件查询对象 Specification
        Specification specification = new Specification() {
            @Override
            // Root 用于获取属性字段,CriteriaQuery可以用于简单条件查询,CriteriaBuilder 用于构造复杂条件查询
            public Predicate toPredicate(Root root,
                    CriteriaQuery query, CriteriaBuilder cb) {
                List list = new ArrayList();

                // 简单单表查询
                if (StringUtils.isNotBlank(courier.getCourierNum())) {
                    Predicate p1 = cb.equal(
                            root.get("courierNum").as(String.class),
                            courier.getCourierNum());
                    list.add(p1);
                }
                if (StringUtils.isNotBlank(courier.getCompany())) {
                    Predicate p2 = cb.like(
                            root.get("company").as(String.class),
                            "%" + courier.getCompany() + "%");
                    list.add(p2);
                }
                if (StringUtils.isNotBlank(courier.getType())) {
                    Predicate p3 = cb.equal(root.get("type").as(String.class),
                            courier.getType());
                    list.add(p3);
                }
                // 多表查询
                Join standardJoin = root.join("standard",
                        JoinType.INNER);
                if (courier.getStandard() != null
                        && StringUtils.isNotBlank(courier.getStandard()
                                .getName())) {
                    Predicate p4 = cb.like(
                            standardJoin.get("name").as(String.class), "%"
                                    + courier.getStandard().getName() + "%");
                    list.add(p4);
                }
                return cb.and(list.toArray(new Predicate[0]));
            }
        };

        // 调用业务层 ,返回 Page
        Page pageData = courierService.findPageData(specification,
                pageable);
        // 将返回page对象
        Map result = new HashMap();
        result.put("total", pageData.getTotalElements());
        result.put("rows", pageData.getContent());
        // 将结果对象 压入值栈顶部
        ActionContext.getContext().getValueStack().push(result);

        return SUCCESS;
    }
  }

Specification对象:

toPredicate方法中Root 用于获取属性字段,CriteriaQuery可以用于简单条件查询,CriteriaBuilder 用于构造复杂条件查询

你可能感兴趣的:(javaweb)