03.SpringDataJPA

SpringDataJPA

Spring Data JPA 让我们解脱了DAO层的操作,基本上所有CRUD都可以依赖于它来实现,在实际的工作工程中,推荐使用Spring Data JPA + ORM(如:hibernate)完成操作,这样在切换不同的ORM框架时提供了极大的方便,同时也使数据库层操作更加简单,方便解耦

一、工程搭建

1). maven坐标

<properties>
    <spring.version>4.2.4.RELEASE</spring.version>
    <hibernate.version>5.0.7.Final</hibernate.version>
    <slf4j.version>1.6.6</slf4j.version>
    <log4j.version>1.2.12</log4j.version>
    <c3p0.version>0.9.1.2</c3p0.version>
    <mysql.version>5.1.6</mysql.version>
</properties>

<dependencies>
    <!-- junit单元测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.9</version>
        <scope>test</scope>
    </dependency>

    <!-- spring beg -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.6.8</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <!-- spring 对orm 的支持 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <!-- spring end -->

    <!-- hibernate beg -->
    <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>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>5.2.1.Final</version>
    </dependency>
    <!-- hibernate end -->

    <!-- c3p0 beg -->
    <dependency>
        <groupId>c3p0</groupId>
        <artifactId>c3p0</artifactId>
        <version>${c3p0.version}</version>
    </dependency>
    <!-- c3p0 end -->

    <!-- log end -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>${log4j.version}</version>
    </dependency>

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>${slf4j.version}</version>
    </dependency>

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>${slf4j.version}</version>
    </dependency>
    <!-- log end -->


    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysql.version}</version>
    </dependency>

    <!-- spring 对 jpa 的支持 -->
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-jpa</artifactId>
        <version>1.9.0.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <!-- el beg 使用spring data jpa 必须引入 -->
    <dependency>
        <groupId>javax.el</groupId>
        <artifactId>javax.el-api</artifactId>
        <version>2.2.4</version>
    </dependency>

    <dependency>
        <groupId>org.glassfish.web</groupId>
        <artifactId>javax.el</artifactId>
        <version>2.2.4</version>
    </dependency>
    <!-- el end -->
</dependencies>


<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.7.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

2). spring容器整合SpringDataJPA配置

根据项目需求修改

<?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: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" xmlns:task="http://www.springframework.org/schema/task"
       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">

    <!-- 1.dataSource 配置数据库连接池; -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/jpa" />
        <property name="user" value="root" />
        <property name="password" value="abc123" />
    </bean>

    <!-- 2.配置entityManagerFactory 工厂 -->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />

        <!-- 【配置实体类的包扫描路径!每个实体类对应一张表,靠注解绑定关系】 -->
        <property name="packagesToScan" value="com.lingting.entity" />

        <property name="persistenceProvider">
            <!-- 持久化方案提供商,这里选择Hibernate -->
            <bean class="org.hibernate.jpa.HibernatePersistenceProvider" />
        </property>
        <!--JPA的供应商适配器,Spring提供的!-->
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <!-- 关闭自动生成表的功能【鸡肋!】 -->
                <property name="generateDdl" value="false" />
                <!-- 枚举类型的数据库类型 -->
                <property name="database" value="MYSQL" />
                <!-- 数据库“方言”配置 -->
                <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
                <!-- 控制台显示执行的sql语句 -->
                <property name="showSql" value="true" />
            </bean>
        </property>

        <!-- jpa “方言”:不同提供商实现的高级特性,可选 -->
        <property name="jpaDialect">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
        </property>

        <!-- 【方便演示,实际中不要配置】鸡肋配置!注入jpa的配置信息
        加载jpa的基本信息和jpa实现方式(hibernate)的配置信息
        hibernate.hbm2ddl.auto:是否自动创建数据库表

        <property name="jpaProperties">
            <props>
                <prop key="hibernate.hbm2ddl.auto">none</prop>
            </props>
        </property>
        -->
    </bean>

    <!-- 整合spring data jpa;动态代理、切面扫描的包路径,dao层的接口写在此中-->
    <jpa:repositories base-package="com.lingting.dao"
                      transaction-manager-ref="transactionManager"
                      entity-manager-factory-ref="entityManagerFactory"/>

    <!-- 3.事务管理器-->
    <!-- JPA事务管理器: 底层估计使用的本地线程  -->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>

    <!-- 声明式事务控制 -->

    <!-- 4.txAdvice 切面 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="save*" propagation="REQUIRED"/>
            <tx:method name="insert*" propagation="REQUIRED"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <tx:method name="delete*" propagation="REQUIRED"/>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <!-- 5.aop 切面表达式-->
    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(* com.lingting.service.*.*(..))" />
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut" />
    </aop:config>

    <!-- 其他注解扫描配置 spring 相关 -->
    <context:component-scan base-package="com.lingting"/>
</beans>

二、实体类与表之间的映射关系配置

以下配置省略get/set/toString方法,

1). 一对多的关系

  1. Customer表,主表, 一的关系

在从表中维护外键信息,主表中参照从表对应的属性即可!

import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

@Entity // 实体类
@Table(name = "cst_customer") // 对应的表关系
public class Customer implements Serializable {

    @Id // 主键
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 主键生成策略
    @Column(name = "cust_id") // 主键字段名
    private Long custId; // 客户主键 属性对应主键字段

    @Column(name = "cust_name")
    private String custName;//客户名称

    /** 配置客户和联系人之间的关系:客户 一对多 联系人
     【实际生产中建议不要使用外键,使用外键的分布式方案下的数据库集群性能是非常糟糕的!】
     @OneToMany 配置一对多关系,参数为有关系的实体类的字节码对象
     一般主表上【放弃对外键的维护】,外键的维护交给从表即可

     mappedBy 表示参照从表中的 customer 属性来配置,也就是说从表中必须有 customer属性!
     cascade 级联操作,主表的增删改同时影响从表记录的增删改!枚举值如下
          CascadeType.ALL    : 所有
          CascadeType.MERGE  : 更新
          CascadeType.PERSIST: 保存
          CascadeType.REMOVE : 删除
     fetch: 配置关联对象的加载方式
           EAGER : 立即加载【从多的一方查一的一方为默认】
           LAZY  : 懒加载【从一的一方查多的一方为默认】
     */
    @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private Set<LinkMan> linkMans = new HashSet<>();

    /** 其他字段和get/set/toString略 */
  1. LinkMan表,从表, 多的关系

不需要额外设置外键属性字段!,在配置相互关系中使用注解配置即可!

import javax.persistence.*;
import java.io.Serializable;

@Entity
@Table(name="cst_linkman")
public class LinkMan implements Serializable {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="lkm_id")
    private Long lkmId;

    @Column(name="lkm_name")
    private String lkmName;

    /** 配置联系人到客户的多对一关系
     @ManyToOne 配置多对一
     @JoinColumn 【配置外键信息】,指向主表的主键
     */
    @ManyToOne(targetEntity = Customer.class)
    @JoinColumn(name = "lkm_cust_id", referencedColumnName = "cust_id")
    private Customer customer;

    /** 其他字段和get/set/toString略,其他字段不需要外键属性 */

2). 多对多的关系

用户选择角色,所以逻辑上主动权在User实体类上

  1. User表,主动的一方

拥有维护主键的权限,不要同时将被动的一方有维护权限,容易出错!

import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

@Entity
@Table(name = "sys_user")
public class User implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private Long userId;

    @Column(name = "user_name")
    private String userName;

    @Column(name = "user_age")
    private Integer age;

    /** 多对多的关系
        被动的一方放弃维护外键的权限,也就是Role实体类中放弃
     fetch: 配置关联对象的加载方式
         EAGER : 立即加载
         LAZY  : 懒加载【默认】
     */
    @ManyToMany(targetEntity = Role.class,cascade = CascadeType.ALL, fetch = FetchType.LAZY) // 多对多关系,需要对方实体类字节码
    @JoinTable(name = "sys_user_role",  // 中间表名称
            // joinColumns: 当前对象在中间表的外键,以及外键指向的当前对象的主键
            joinColumns = {@JoinColumn(name = "sys_user_id", referencedColumnName = "user_id")},
            // 对方对象在中间表的外键,以及外键指向的对方对象的主键
            inverseJoinColumns = {@JoinColumn(name = "sys_role_id", referencedColumnName = "role_id")}
    )
    private Set<Role> roles = new HashSet<>();

    /** 其他字段和get/set/toString略 */
  1. Role表,被动的一方
import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

@Entity
@Table(name = "sys_role")
public class Role implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "role_id")
    private Long roleId;

    @Column(name = "role_name")
    private String roleName;

    /**
     配置多对多,但是放弃外键维护的权限
     */
    @ManyToMany(mappedBy = "roles")
    private Set<User> users = new HashSet<>();
    /** 其他字段和get/set/toString略 */

三、动态代理dao层接口配置

1). CustomerDao

import com.lingting.entity.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

import java.util.List;

/** 集成了如下接口的接口就符合 SpringDataJPA 规范【只是spring设计的规范】
 * JpaRepository<T, ID>         简单查询
 * JpaSpecificationExecutor<T>  高级查询
 *     T 泛型为 pojo实体类
 *     ID 为pojo中主键字段的数据类型
 *  底层估计是用【动态代理】实现的动态代理类!
 */
public interface CustomerDao extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {

     /** 后面介绍的一些与Customer实体类相关的接口抽象方法写在此! */
}

2). 其他dao

...

public interface LinkManDao extends JpaRepository<LinkMan, Long>, JpaSpecificationExecutor<LinkMan> { }

public interface RoleDao extends JpaRepository<Role, Long>, JpaSpecificationExecutor<Role> { }

public interface UserDao extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> { }

四、单表操作

测试类模板

import com.lingting.entity.Customer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.persistence.criteria.*;
import java.util.List;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class JpaSpecificationExecutorTest {
    @Autowired
    private CustomerDao customerDao;

    @Autowired
    private LinkManDao linkManDao;

    @Autowired
    private UserDao userDao;

    @Autowired
    private RoleDao roleDao;

    /** 下方的测试方法写在此 */
}

1). 使用JpaRepository接口中提供的原生抽象方法

/** ~~~~~~~~~~~~~~~~~ JpaRepository接口中的原生方法 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
/**
 * 立即查询:根据id进行查询
 * 立即加载 em.find()
 */
@Test
public void findOne() {
    Customer one = customerDao.findOne(5L);
    System.out.println(one);
}

/**
 * 根据id查询
 * 懒加载:由于设计上的原因,需要加上 事务注解,否则会报错
 *  em.getReference()
 */
@Test
@Transactional
public void getOne() {
    Customer one = customerDao.getOne(4L);
    System.out.println(one);
}

/**
 * 查询所有; jdk8+
 */
@Test
public void findAll() {
    List<Customer> all = customerDao.findAll();
    all.forEach(System.out::println);
}

/**
 * save:
 *     新增:id为null
 *     跟新:id不为null
 *          跟新的步骤:必须先查询,然后跟新!否则null会覆盖原有值!
 */
@Test
public void save() {
    Customer c = new Customer();
    c.setCustName("红岸工程");
    c.setCustIndustry("外星生命探索");
    customerDao.save(c);

    // 根据id 查询,然后跟新
    Customer one = customerDao.findOne(6L);
    one.setCustIndustry("外星生命探索");
    customerDao.save(one);
}

/**
 * 删除
 */
@Test
public void delete() {
    customerDao.delete(6L);
}

/**
 * 聚合查询
 */
@Test
public void aggregation() {
    long count = customerDao.count();
    System.out.println(count);
}

/**
 * 根据id判断对象是否存在
 */
@Test
public void exists() {
    boolean exists = customerDao.exists(3L);
    System.out.println(exists);
}

2). 使用jpql方式,在接口中添加自定义抽象方法

  1. CustomerDao接口中的抽象方法
/** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * jpql 根据客户名进行查询
 * ?占位符后面的索引代表签名的索引,从1开始!
 */
@Query(value = "from Customer where custName = ?1 and custIndustry = ?2")
public List<Customer> findByCustName(String custName, String custIndustry);

/**
 * 根据id跟新客户名
 * @Query: 这里用于存放 jpql语句【设计的真不行,一个跟新语句为何要用Query注解?】
 * @Modifying: 指定为跟新操作
 */
@Query("update Customer set custName = ?2 where custId = ?1")
@Modifying
public void updateCustomer(long custId, String custName);
  1. 测试类中的方法
/** ~~~~~~~~~~~~~~~~~ jpql,在接口中添加抽象方法 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */

@Test
public void findByCustName() {
    List<Customer> list = customerDao.findByCustName("红岸工程", "外星生命探索");
    list.forEach(System.out::println);
}

/** jpql 的跟新/删除操作
 * @Transactional: 需要手动添加事务
 * @Rollback(value=false): Junit默认回滚事务,需要修改【Junit中需要,service层中是不需要的!】
 */
@Test
@Transactional
@Rollback(value = false)
public void updateCustomer() {
    customerDao.updateCustomer(4L, "星环城");
}

3). 使用彻底的方法名称进行查询

jpql更加深入的封装,直接解析方法名,需要在dao中进行定义,以便动态代理进行增强

  1. CustomerDao接口中的抽象方法

/** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * 方法名称规则查询【没得注解,对jpql更加深入的封装】
 * 注意参数的个数和位置要对应!
 * 只需要定义SpringDataJPA方法命名的规则进行命名方法名,就可以进行指定查询!
 * 如下几种方式:【可组合: findBy+属性名+查询方式+And+属性名...】
 *      findByAttribute
 *      findByAttr1AndAttr2...
 *      findByAttributeLike
 *      findByAttributeIsNull
 *      findByLastnameOrFirstname
 *      ...
 */
public List<Customer> findByCustNameAndCustIndustry(String custName, String custIndustry);

public List<Customer> findByCustNameLike(String custName);

public List<Customer> findByCustNameStartingWith(String custName);
  1. 测试类中的方法
/** ~~~~~~~~~~~~~~~~~ 方法名称规范查询 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */

@Test
public void findByCustNameAndCustIndustry() {
    List<Customer> list = customerDao.findByCustNameAndCustIndustry("红岸工程", "外星生命探索");
    list.forEach(System.out::println);
}

@Test
public void findByCustNameLike() {
    List<Customer> byCustNameLike = customerDao.findByCustNameLike("星%");
    byCustNameLike.forEach(System.out::println);
}

@Test
public void findByCustNameStartingWith() {
    List<Customer> list = customerDao.findByCustNameStartingWith("新");
    list.forEach(System.out::println);
}
  1. 方法名称转换对应关系
Keyword Sample JPQL
And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
Is,Equals findByFirstnameIs,findByFirstnameEquals … where x.firstname = ?1
Between findByStartDateBetween … where x.startDate between ?1 and ?2
LessThan findByAgeLessThan … where x.age < ?1
LessThanEqual findByAgeLessThanEqual … where x.age <= ?1
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)

4). 使用JpaSpecificationExecutor接口中提供的原生抽象方法

组合查询、排序、分页综合查询如下

/**
 * 自定义查询条件
 * 实现 Specification接口,提供查询对象的泛型
 * 实现toPredicate方法,构造查询条件
 * 使用方法中的两个参数即可【还有一个为顶层查询条件,几乎不会使用】
 *      Root: 获取需要查询的对象属性
 *      CriteriaBuilder: 构造查询条件,内部封装了很多查询条件(模糊匹配,精准匹配)
 */
@Test
public void dynamicQuery() {
    Specification<Customer> spec = new Specification<Customer>() {
        @Override
        public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            // 案例:

            // 1.获取被比较的属性
            Path<Object> custName = root.get("custName");
            Path<Object> custIndustry = root.get("custIndustry");
            Path<Object> custLevel = root.get("custLevel");

            // 2.构造查询条件:参数:被比较的对象、比较的值

            // equal
            Predicate p1 = cb.equal(custName, "星环城");

            // 其他方式
            Predicate p2 = cb.like(custIndustry.as(String.class), "太空%");

            // gt 大于; ge 大于等于 lt/le
            Predicate p3 = cb.gt(custLevel.as(int.class), 1);

            // 逻辑组合关系,可变长参数!
            Predicate p4 = cb.or(p2, p3);
            Predicate p5 = cb.and(p1, p4);

            return p5;
        }
    };
    // 当返回值之后一个时,可使用:
    // Customer c = customerDao.findOne(spec);

    List<Customer> list1 = customerDao.findAll(spec);

    // 排序,第一个参数为排序方式(枚举);第二个为需要排序的字段
    Sort sort = new Sort(Sort.Direction.DESC, "custId");
    List<Customer> list2 = customerDao.findAll(spec, sort);

    // 分页查询, 参数1:查询的页数,参数2:每页的条数
    Pageable pageable = new PageRequest(2 - 1, 2);
    Page<Customer> page = customerDao.findAll(spec, pageable);
    List<Customer> content = page.getContent();
    long totalElements = page.getTotalElements();
    int totalPages = page.getTotalPages();

    content.forEach(System.out::println);
    System.out.println(totalElements);
    System.out.println(totalPages);

}

CriteriaBuilder对象方法对应关系表

方法名称 Sql对应关系
equle filed = value
gt(greaterThan ) filed > value
lt(lessThan ) filed < value
ge(greaterThanOrEqualTo ) filed >= value
le( lessThanOrEqualTo) filed <= value
notEqule filed != value
like filed like value
notLike filed not like value

5). 【了解】使用原生sql查询,在dao中进行定义

  1. dao
/** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * 使用原生sql查询;【了解】
 * 将@Query注解的nativeQuery参数设置为true
 * 返回值很奇怪,将每条记录的字段放入一个Object[] 中!
 * 还得自己封装成pojo,多麻烦!
 */
@Query(value = "select * from cst_customer where cust_name like ?1", nativeQuery = true)
public List<Object[]> findAllByNative(String custName);
  1. 测试
/** ~~~~~~~~~~~~~~~~~ 【了解】原生sql查询 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */

/**
 * 使用原生的sql语句进行查询【了解】
 */
@Test
public void findAllByNative() {
    List<Object[]> list = customerDao.findAllByNative("星%");
    for (Object[] objects : list) {
        System.out.println(Arrays.toString(objects));
    }
}

五、多表测试

多表主要是配置,见上面的实体类配置;

1). 级联测试

注意:在实际开发中,级联删除请慎用!(在一对多的情况下)

/** 一对多
 * 保存一个客户,一个联系人
 */
@Test
@Transactional
@Rollback(value = false)
public void addOne() {
    Customer customer = new Customer();
    customer.setCustName("华为");

    LinkMan linkMan = new LinkMan();
    linkMan.setLkmName("柔丝");

    // 级联操作,两个关联都需要设置
    customer.getLinkMans().add(linkMan);
    linkMan.setCustomer(customer);

    // 级联操作后,只需要保存一个
    customerDao.save(customer);
}

/** 多对多
 */
@Test
@Transactional
@Rollback(false)
public void add() {
    // 新建实体类
    User user = new User();
    user.setUserName("小马");

    Role role = new Role();
    role.setRoleName("CEO");

    // 绑定关系
    user.getRoles().add(role);
    role.getUsers().add(user);

    // 级联操作,只操作主表
    userDao.save(user);
}

2). 对象导航测试

/** 一对多 对象导航查询
 * 对象导航查询:
 *  jpa的机制,通过 对象.get关联的属性() 即可进行懒加载查询其他表相关联的数据
 *  注意:在junit中需要加上@Transactional事务注解解决 no session问题
 */
@Test
@Transactional
public void objectNavigate() {
    Customer customer = customerDao.findOne(1L);

    // 下面的执行过程为懒加载机制【获取的是linkMans的一个动态代理对象】【注意idea工具导致的假象】
    Set<LinkMan> linkMans = customer.getLinkMans();

    // 执行下面时才开始查询!
    linkMans.forEach(System.out::println);
}

3). 使用Specification查询

/**
 * Specification的多表查询
 */
@Test
public void testFind() {
    Specification<LinkMan> spec = new Specification<LinkMan>() {
        public Predicate toPredicate(Root<LinkMan> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            //Join代表链接查询,通过root对象获取
            //创建的过程中,第一个参数为关联对象的属性名称,第二个参数为连接查询的方式(left,inner,right)
            //JoinType.LEFT : 左外连接,JoinType.INNER:内连接,JoinType.RIGHT:右外连接
            Join<LinkMan, Customer> join = root.join("customer", JoinType.INNER);
            return cb.like(join.get("custName").as(String.class),"传智播客1");
        }
    };
    List<LinkMan> list = linkManDao.findAll(spec);
    for (LinkMan linkMan : list) {
        System.out.println(linkMan);
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349

推荐阅读更多精彩内容

  • 这部分主要是开源Java EE框架方面的内容,包括Hibernate、MyBatis、Spring、Spring ...
    杂货铺老板阅读 1,357评论 0 2
  • 一. Java基础部分.................................................
    wy_sure阅读 3,805评论 0 11
  • 在一个方法内部定义的变量都存储在栈中,当这个函数运行结束后,其对应的栈就会被回收,此时,在其方法体中定义的变量将不...
    Y了个J阅读 4,413评论 1 14
  • 世界上最美的不是日出时的天空之城,也不是雨过天晴的绚烂彩虹,而是玩耍时孩童纯真的笑脸。 这个暑假家里忙着装修,看孩...
    北梦沐曦阅读 805评论 0 1
  • 你又开始躲藏,又开始游离,翻看你的手机是多么愚蠢的事,愚蠢在我认输了,我在乎你监控你,我多想做个高傲的什么都...
    回归的旅程阅读 152评论 0 0