Skip to content
标签
spring
数据库
字数
4867 字
阅读时间
22 分钟

一、概述

是 spring data 项目下的一个模块。提供了一套基于 JPA 标准操作数据库的简化方案。底层默认的是依赖 Hibernate JPA 来实现的。

Spring Data JPA 的技术特点:我们只需要定义接口并集成 Spring Data JPA 中所提供的接 口就可以了。不需要编写接口实现类。

1.1 重要api

entityManagerFactory

EntityManagerFactory接口主要用来创建EntityManager实例 EntityManagerFactory是一个线程安全的对象,并且其创建极其浪费资源,所以编程的时候要保持它是单例的。 在JPA规范中,EntityManager是操作数据库的重要API,他是线程不安全的,需要保持线程独有。 重要方法: getTransaction: 获取事务对象 persist:保存操作 merge:更新操作 remove:删除操作 find/getReference:根据id查询

1.2 实现过程

jpa的dao层对象本质上是通过JdkDynamicAopProxy生成的一个代理对象 当进行操作时,会通过JdkDynamicAopProxy的invoke方法,对操作dao层对象生成动态代理对象SimpleJpaRepository。代理对象中包含Jpa规范的API操作 Spring Data JPA只是对标准JPA操作进行了进一步封装,底层是JPA原生实现方式。还是使用的EntityManager。

二、使用示例

2.1 入门Demo

依赖

xml
<!-- Mysql and MariaDB -->
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<version>5.1.6</version>
</dependency>

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

配置

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: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.xsd 
                           http://www.springframework.org/schema/context 
                           http://www.springframework.org/schema/context/spring-context.xsd 
                           http://www.springframework.org/schema/aop 
                           http://www.springframework.org/schema/aop/spring-aop.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">
    <!-- 配置读取 properties 文件的工具类 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!-- 配置 c3p0 数据库连接池 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="driverClass" value="${jdbc.driver.class}"/> 
        <property name="user" value="${jdbc.username}"/> 
        <property name="password" value="${jdbc.password}"/> 
    </bean> 
    <!-- Spring 整合 JPA 配置 EntityManagerFactory--> 
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> 
        <property name="dataSource" ref="dataSource"/> <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> 
            <!-- hibernate 相关的属性的注入 --> 
            <!-- 配置数据库类型 --> 
            <property name="database" value="MYSQL"/> 
            <!-- 正向工程 自动创建表 --> 
            <property name="generateDdl" value="true"/> 
            <!-- 显示执行的 SQL --> 
            <property name="showSql" value="true"/> 
        </bean> 
        </property> 
        <!-- 扫描实体的包 --> 
        <property name="packagesToScan"> 
            <list> 
                <value>com.bjsxt.pojo</value> 
            </list> 
        </property> 
    </bean> 
    <!-- 配置 Hibernate 的事务管理器 --> 
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> 
        <property name="entityManagerFactory" ref="entityManagerFactory"/> 
    </bean> 
    <!-- 配置开启注解事务处理 --> 
    <tx:annotation-driven transaction-manager="transactionManager"/>
    <!-- 配置 springIOC 的注解扫描 --> 
    <context:component-scan base-package="com.bjsxt"/> 
    <!-- Spring Data JPA 的配置 -->
    <!-- base-package:扫描 dao 接口所在的包 --> 
    <jpa:repositories base-package="com.bjsxt.dao"/>
</beans>

实体类

java
@Entity//表示这是一个实体类
@Table(name = "article") //建立实体类和表的映射关系
public class Article implements Serializable {
  @Id//声明当前私有属性为主键
    @GeneratedValue(strategy = GenerationType.IDENTITY) //配置主键的生成策略 
  private Integer aid;
    
    //声明类的属性跟数据表字段的对应关系,如果属性名称和字段名称一致,可省略
    @Column(name = "title")
    private String title; 
    private String author; 
    private Date createTime;
}

dao层接口

java
/**
* JpaRepository<实体类类型,主键类型>:用来完成基本 CRUD 操作 
* JpaSpecificationExecutor<实体类类型>:用于复杂查询(分页等查询操作)
*/
public interface UsersDao extends JpaRepository<Users, Integer> {
}

使用方式

java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class UsersDaoImplTest { 
    @Autowired 
    private UsersDao usersDao;
    /**
    * 添加用户 
    */ 
    @Test
    @Transactional// 在测试类对于事务提交方式默认的是回滚。
    @Rollback(false)//取消自动回滚 
    public void testInsertUsers(){
        Users users = new Users();
        users.setUserage(24); 
        users.setUsername("张三");
        this.usersDao.save(users); 
    } 
}

三、核心知识

3.1 运行原理

java
@PersistenceContext(name="entityManagerFactory") 
private EntityManager em;
@Test 
public void test1(){ 
    //org.springframework.data.jpa.repository.support.SimpleJpaRepositor y@fba8bf
    //System.out.println(this.usersDao); 
    //class com.sun.proxy.$Proxy29 代理对象 是基于 JDK 的动态代理方式 创建的 
    //System.out.println(this.usersDao.getClass());
    JpaRepositoryFactory factory = new JpaRepositoryFactory(em); 
    //getRepository(UsersDao.class);可以帮助我们为接口生成实现类。而 这个实现类是 SimpleJpaRepository 的对象 
    //要求:该接口必须要是继承 Repository 接口 
    UsersDao ud = factory.getRepository(UsersDao.class);
    System.out.println(ud); System.out.println(ud.getClass());
}

3.2 Repository 接口

Repository 接口

Repository 接口是 Spring Data JPA 中为我我们提供的所有接口中的顶层接口

提供了两种查询方式

  1. 基于方法名称命名规则查询
  2. 基于@Query 注解查询 规则:findBy(关键字)+属性名称(属性名称的首字母大写)+查询条件(首字母大写) 如:findByNameAndPwd

使用示例

java
public interface UsersDao extends Repository<Users, Integer> { 
    /** 
    按照Spring Data JPA 定义的规则,查询方法以findBy开头,涉及条件查询
    时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写。框
    架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进
    行解析。
    * 判断相等的条件,有三种表示方式 
    * 1,什么都不写,默认的就是做相等判断 
    * 2,Is 
    * 3,Equal 
    */
    List<Users> findByUsernameIs(String string); 
    List<Users> findByUsernameLike(String string);
    List<Users> findByUsernameAndUserageGreaterThanEqual(String name,Integer age); 
    
    // Query注解查询
    //JPQL:通过 Hibernate 的 HQL 演变过来的。他和 HQL 语法及其相似。
    @Query(value="from Users where username = ?")
    List<Users> queryUserByNameUseJPQL(String name);
    @Query("update Users set userage = ? where userid = ?")
    @Modifying //@Modifying 当前语句是一个更新语句 
    void updateUserAgeById(Integer age,Integer id);
    // 通过SQL语句查询
    //nativeQuery:默认的是 false.表示不开启 sql 查询。是否对 value 中的语句 做转义。
    @Query(value="select * from t_users where username = ?",nativeQuery=true) 
    List<Users> queryUserByNameUseSQL(String name);
}

CrudRepository 接口

基本增删改查

java
// 保存	save  
//批量保存  save
//根据id查询	findOne
//查询全部		findAll
//根据id删除	delete
/**
* 更新数据 方式一 
*/ 
public void test(){
    Users user = this.usersDao.findOne(12);
    user.setUsername("王一");
    this.usersDao.save(user); 
}
/**
* 更新数据 方式二 
*/
@Transactional 
@Rollback(false) 
public void test(){ 
    Users user = this.usersDao.findOne(12);
    //持久化状态的 
    user.setUsername("王二");
}

PagingAndSortingRepository 接口

分页查询

java
int page = 2; //page:当前页的索引。注意索引都是从 0 开始的。
int size = 3;// size:每页显示 3 条数据 
Pageable pageable= new PageRequest(page, size); 
Page<Users> p = this.usersDao.findAll(pageable);

//Sort:该对象封装了排序规则以及指定的排序字段(对象的属性来表示) 
//direction:排序规则 
//properties:指定做排序的属性
Sort sort = new Sort(Direction.DESC,"userid");
//多列的排序处理
Order order1 = new Order(Direction.DESC,"userage");
Order order2 = new Order(Direction.ASC,"username");
Sort sort = new Sort(order1,order2); 
List<Users> list = (List<Users>)this.usersDao.findAll(sort)

JpaRepository 接口

返回值适配处理

java
// JpaRepository 接口是开发时使用的最多的接口。其特点是可以将其他接口的方法的返回值做适配处理。可以在开发时更方便的使用这些方法。

JpaSpecificationExecutor 接口

多条件查询

java
// 单条件查询
Specification<Users> spec = new Specification<Users>() {
    /**
    * @return Predicate:定义了查询条件 
    * @param Root<Users> root:根对象。封装了查询条件的对象
    * @param CriteriaQuery<?> query:定义了一个基本的查询.一般不 使用
    * @param CriteriaBuilder cb:创建一个查询条件 
    */ 
    @Override 
    public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> query, CriteriaBuilder cb) { 
        Predicate pre = cb.equal(root.get("username"), "王五"); 
        return pre;
    } 
};
List<Users> list = this.usersDao.findAll(spec);

// 多条件1
Specification<Users> spec = new Specification<Users>() {
    @Override 
    public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> query, CriteriaBuilder cb) { 
        List<Predicate> list = new ArrayList<>(); 
        list.add(cb.equal(root.get("username"),"王五")); 
        list.add(cb.equal(root.get("userage"),24)); 
        //此时条件之间是没有任何关系的。
        Predicate[] arr = new Predicate[list.size()]; 
        return cb.and(list.toArray(arr));
    }
};
List<Users> list = this.usersDao.findAll(spec);
// 多条件2
Specification<Users> spec = new Specification<Users>() {
    @Override 
    public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        return cb.or(cb.equal(root.get("username"),"王五 "),cb.equal(root.get("userage"), 25));
    } 
};
List<Users> list = this.usersDao.findAll(spec);

// 分页与排序
//排序等定义 
Sort sort = new Sort(Direction.DESC,"userid"); 
// List<Users> list = this.usersDao.findAll(spec, sort);
//分页的定义 
Pageable pageable = new PageRequest(2,2, sort);
Page<Users> page = this.usersDao.findAll(spec, pageable);

3.3 关联映射操作

一对一的关联关系

java
//user类属性
@OneToOne 
//@JoinColumn:就是维护一个外键 
@JoinColumn(name="roles_id", referencedColumnName = "aid", unique = true) 
private Roles roles;

//role类属性
@OneToOne(mappedBy="roles",cascade = CascadeType.ALL)) 
private Users users;


//关系操作
//创建角色 
Roles roles = new Roles();
roles.setRolename("管理员"); 
//创建用户 
Users users = new Users(); 
users.setUserage(30);
users.setUsername("赵小刚"); 
//建立关系 
users.setRoles(roles); 
roles.setUsers(users); 
//保存数据 
this.usersDao.save(users);

一对多关联关系

java
//一个角色对应多个用户
//用户实体
@ManyToOne()
@JoinColumn(name="roles_id",referencedColumnName = "aid") 
private Roles roles;

//角色实体
@OneToMany(mappedBy="roles") 
private Set<Users> users = new HashSet<>();

//关系操作
//创建角色
Roles roles = new Roles();
roles.setRolename("管理员");
//创建用户 
Users users =new Users(); 
users.setUserage(30); 
users.setUsername("小王"); 
//建立关系 
roles.getUsers().add(users); 
users.setRoles(roles); 
//保存数据 
this.usersDao.save(users);
/**
*@OneToMany:
   	作用:建立一对多的关系映射
    属性:
    	targetEntityClass:指定多的多方的类的字节码
    	mappedBy:指定从表实体类中引用主表对象的名称。
    	cascade:指定要使用的级联操作
    	fetch:指定是否采用延迟加载
    	orphanRemoval:是否使用孤儿删除

@ManyToOne
    作用:建立多对一的关系
    属性:
    	targetEntityClass:指定一的一方实体类字节码
    	cascade:指定要使用的级联操作
    	fetch:指定是否采用延迟加载
    	optional:关联是否可选。如果设置为false,则必须始终存在非空关系。
@JoinColumn
作用:用于定义主键字段和外键字段的对应关系。
     属性:
    	name:指定外键字段的名称
    	referencedColumnName:指定引用主表的主键字段名称
    	unique:是否唯一。默认值不唯一
    	nullable:是否允许为空。默认值允许。
    	insertable:是否允许插入。默认值允许。
    	updatable:是否允许更新。默认值允许。
    	columnDefinition:列的定义信息
*/

多对多关联关系

java
//一个角色可以拥有多个菜单,一个菜单可以分配多个角色。多对多的关联关系
//角色实体
@ManyToMany 
//@JoinTable:配置中间表信息 
//joinColumns:建立当前表在中间表中的外键字段
 //中间表的外键字段关联对方类所对应表的主键字段
@JoinTable(name="t_roles_menus",joinColumns=@JoinColumn(name="ro le_id"),inverseJoinColumns=@JoinColumn(name="menu_id")) 
private Set<Menus> menus = new HashSet<>();

//菜单实体
@ManyToMany(mappedBy="menus") 
private Set<Roles> roles = new HashSet<>();

//关联操作
//创建角色对象 
Roles roles = new Roles();
roles.setRolename("超级管理员"); 
//创建菜单对象 XXX 管理平台 --->用户管理 
Menus menus = new Menus();
menus.setMenusname("XXX 管理平台");
menus.setFatherid(-1); 
menus.setMenusurl(null); 
//用户管理菜单
Menus menus1 = new Menus();
menus1.setMenusname("用户管理");
menus1.setFatherid(1); 
menus1.setMenusurl(null);
//建立关系 
roles.getMenus().add(menus); 
roles.getMenus().add(menus1);
menus.getRoles().add(roles); 
menus1.getRoles().add(roles);
//保存数据 
this.rolesDao.save(roles);
/**
@ManyToMany
	作用:用于映射多对多关系
	属性:
		cascade:配置级联操作。
		fetch:配置是否采用延迟加载。
    	targetEntity:配置目标的实体类。映射多对多的时候不用写。

@JoinTable
    作用:针对中间表的配置
    属性:
    	nam:配置中间表的名称
    	joinColumns:中间表的外键字段关联当前实体类所对应表的主键字段			  			
    	inverseJoinColumn:中间表的外键字段关联对方表的主键字段
    	
@JoinColumn
    作用:用于定义主键字段和外键字段的对应关系。
    属性:
    	name:指定外键字段的名称
    	referencedColumnName:指定引用主表的主键字段名称
    	unique:是否唯一。默认值不唯一
		nullable:是否允许为空。默认值允许。
    	insertable:是否允许插入。默认值允许。
    	updatable:是否允许更新。默认值允许。
    	columnDefinition:列的定义信息。
*/

3.4 实体类与数据库表映射

java
/**
*		* 所有的注解都是使用JPA的规范提供的注解,
 *		* 所以在导入注解包的时候,一定要导入javax.persistence下的
 */
@Entity //声明实体类
@Table(name="cst_customer") //建立实体类和表的映射关系
public class Customer {
	
	@Id//声明当前私有属性为主键
	@GeneratedValue(strategy=GenerationType.IDENTITY) //配置主键的生成策略
	@Column(name="cust_id") //指定和表中cust_id字段的映射关系
	private Long custId;
	
	@Column(name="cust_name") //指定和表中cust_name字段的映射关系
	private String custName;
}
/**
*@Entity
        	作用:指定当前类是实体类。
        @Table
        	作用:指定实体类和表之间的对应关系。
        	属性:
        		name:指定数据库表的名称
        @Id
        	作用:指定当前字段是主键。
        @GeneratedValue
        	作用:指定主键的生成方式。。
        	属性:
        		strategy :指定主键生成策略。
        			IDENTITY:主键由数据库自动生成(主要是自动增长型)
        			SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。
        			AUTO:主键由程序控制
        			TABLE:使用一个特定的数据库表格来保存主键
        @Column
        	作用:指定实体类属性和数据库表之间的对应关系
        	属性:
        		name:指定数据库表的列名称。
        		unique:是否唯一  
        		nullable:是否可以为空  
        		inserttable:是否可以插入  
        		updateable:是否可以更新  
        		columnDefinition: 定义建表时创建此列的DDL  
        		secondaryTable: 从表名。如果此列不建在主表上(默认建在主表),该属性定义该列所在从表的名字搭建开发环境[重点]
*/

3.5 查询方式

父接口方法查询

自定义的Dao接口可以使用它的父接口提供的方法。

方法命名规则查询

方法命名规则查询就是根据方法的名字,就能创建查询。只需要按照SpringData JPA提供的方法命名规则定义方法的名称,就可以完成查询工作。 SpringData JPA在程序执行的时候会根据方法名称进行解析,并自动生成查询语句进行查询. 按照SpringData JPA定义的规则,查询方法以findBy开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉, 然后对剩下部分进行解析。

java
public interface ArticleDao extends JpaRepository<Article, Integer>, JpaSpecificationExecutor<Article> {
    //根据标题查询
    List<Article> findByTitle(String title); 
    //根据标题模糊查询 
    List<Article> findByTitleLike(String title); 
    //根据标题和作者查询
    List<Article> findByTitleAndAuthor(String title, String author); 
    //根据ID范围查询 
    List<Article> findByAidBetween(Integer starAid, Integer endAid); 
    List<Article> findByAidLessThan(Integer endAid);
    List<Article> findByAidIn(List<Integer> aids); 
    //根据创建时间之后查询 
    List<Article> findByCreateTimeAfter(Date createTime); 
}

命名规则示例:

关键字例子对应的JPQL语句
AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ? 2
OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
Is,EqualsfindByFirstnameIs, findByFirstnameEquals… where x.firstname = ?1
BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2
LessThanfindByAgeLessThan… where x.age < ?1
LessThanEqualfindByAgeLessThanEqual… where x.age ⇐ ?1
GreaterThanfindByAgeGreaterThan… where x.age > ?1
GreaterThanEqualfindByAgeGreaterThanEqual… where x.age >= ?1
AfterfindByStartDateAfter… where x.startDate > ?1
BeforefindByStartDateBefore… where x.startDate < ?1
IsNullfindByAgeIsNull… where x.age is null
IsNotNull,NotNullfindByAge(Is)NotNull… where x.age not null
LikefindByFirstnameLike… where x.firstname like ?1
NotLikefindByFirstnameNotLike… where x.firstname not like ?1
StartingWithfindByFirstnameStartingWith… where x.firstname like ?1 (parameter bound with appended %)
EndingWithfindByFirstnameEndingWith… where x.firstname like ?1 (parameter bound with prepended %)
ContainingfindByFirstnameContaining… where x.firstname like ?1 (parameter bound wrapped in %)
OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname desc
NotfindByLastnameNot… where x.lastname <> ?1
InfindByAgeIn(Collection ages)… where x.age in ?1
NotInfindByAgeNotIn(Collection age)… where x.age not in ?1
TRUEfindByActiveTrue()… where x.active = true
FALSEfindByActiveFalse()… where x.active = false
IgnoreCasefindByFirstnameIgnoreCase… where UPPER(x.firstame) = U

JPQL查询

使用SpringData JPA提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来说,我们还 需要灵活的构造查询条件,这时就可以使用@Query注解,结合JPQL的语句方式完成查询。 JPQL,全称是Java Persistence Query Language。JPQL语句是JPA中定义的一种查询语言,此种 语言的用意是让开发者忽略数据库表和表中的字段,而关注实体类及实体类中的属性。 它的写法十分类似于SQL语句的写法,但是要把查询的表名换成实体类名称,把表中的字段名换成实体类 的属性名称。

java
public interface ArticleDao extends JpaRepository<Article, Integer>, JpaSpecificationExecutor<Article> {
    //展示位置参数绑定 
    @Query("from Article a where a.author=?1 and a.title=?2") 
    List<Article> findByCondition1(String author, String title);
    //展示名字参数绑定 
    @Query("from Article a where a.author=:author and a.title=:title") 
    List<Article> findByCondition2(@Param("author") String author, @Param("title") String title);
    
    //展示like模糊查询 
    @Query("from Article a where a.title like %:title%")
    List<Article> findByCondition3(@Param("title") String title); 
    
    //展示排序查询 
    @Query("from Article a where a.title like %:title% order by aid desc") 
    List<Article> findByCondition4(@Param("title") String title);
    
    //展示分页查询 
    @Query("from Article a where a.title like %:title%") 
    Page<Article> findByCondition5(Pageable pageable, @Param("title") String title);
    
    //展示传入集合参数查询
    @Query("from Article a where a.aid in :aids")
    List<Article> findByCondition6(@Param("aids") Collection<String> aids);
    
    //展示传入Bean进行查询(SPEL表达式查询) 
    @Query("from Article a where a.author=:#{#article.author} and a.title=:# {#article.title}") 
    Article findByCondition67(@Param("article") Article article);
}

SQL查询

java
//nativeQuery=true表示使用本地SQL查询 
//基本不会使用,除非是出现非常复杂的业务情况导致SQL非常复杂,JPQL搞不定的时候 
@Query("select * from article where title like ?1 and author = ? 2",nativeQuery=true) 
List<User> findAllByTitleAndAuthor(String title,String author);

Specifications动态查询

有时在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在 Spring Data JPA 中可以通过 JpaSpecificationExecutor 接口查询。相比 JPQL,其优势是类型安全,更加的面向对象,缺点是书写比较麻烦。

java
package com.itheima.test;

import com.itheima.dao.ArticleDao;
import com.itheima.domain.Article;
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 org.springframework.util.StringUtils;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-jpa.xml")
public class Query4Test {

    @Autowired
    private ArticleDao articleDao;


    //按照标题和作者进行查询,以不为空的属性作为查询条件
    @Test
    public void testFindAll() {

        //就模拟从从外边传入的变量
        String title = "黑马程序员3";
        String author = "黑马3";

        List<Article> articles = articleDao.findAll(new Specification<Article>() {

            /**
             * @param root  代表实体对象,我们可以通过它获取属性值
             * @param cq    用于生成SQL语句
             * @param cb    用于拼接查询条件
             * @return
             */
            @Override
            public Predicate toPredicate(Root<Article> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {

                List<Predicate> list = new ArrayList<>();
                if (!StringUtils.isEmpty(title)) {
                    //拼接作为查询条件
                    Predicate predicate = cb.equal(root.get("title").as(String.class), title);
                    list.add(predicate);
                }
                if (!StringUtils.isEmpty(author)) {
                    //拼接作为查询条件
                    Predicate predicate = cb.equal(root.get("author").as(String.class), author);
                    list.add(predicate);
                }

                return cb.and(list.toArray(new Predicate[]{}));
            }
        });

        for (Article article : articles) {
            System.out.println(article);
        }
    }


    @Test
    public void testFindAllWithPage() {

        //就模拟从从外边传入的变量
        String title = "";
        String author = "";

        //分页
        Pageable pageable = PageRequest.of(0, 3);

        Page<Article> page = articleDao.findAll(new Specification<Article>() {

            /**
             * @param root  代表实体对象,我们可以通过它获取属性值
             * @param cq    用于生成SQL语句
             * @param cb    用于拼接查询条件
             * @return
             */
            @Override
            public Predicate toPredicate(Root<Article> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {

                List<Predicate> list = new ArrayList<>();
                if (!StringUtils.isEmpty(title)) {
                    //拼接作为查询条件
                    Predicate predicate = cb.equal(root.get("title").as(String.class), title);
                    list.add(predicate);
                }
                if (!StringUtils.isEmpty(author)) {
                    //拼接作为查询条件
                    Predicate predicate = cb.equal(root.get("author").as(String.class), author);
                    list.add(predicate);
                }

                return cb.and(list.toArray(new Predicate[]{}));
            }
        }, pageable);

        for (Article article : page.getContent()) {
            System.out.println(article);
        }
    }


    @Test
    public void testFindAllWithPageAndSort() {

        //就模拟从从外边传入的变量
        String title = "";
        String author = "";

        //分页
        Pageable pageable = PageRequest.of(0, 3, Sort.by(Sort.Order.desc("aid")));

        Page<Article> page = articleDao.findAll(new Specification<Article>() {

            /**
             * @param root  代表实体对象,我们可以通过它获取属性值
             * @param cq    用于生成SQL语句
             * @param cb    用于拼接查询条件
             * @return
             */
            @Override
            public Predicate toPredicate(Root<Article> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {

                List<Predicate> list = new ArrayList<>();
                if (!StringUtils.isEmpty(title)) {
                    //拼接作为查询条件
                    Predicate predicate = cb.equal(root.get("title").as(String.class), title);
                    list.add(predicate);
                }
                if (!StringUtils.isEmpty(author)) {
                    //拼接作为查询条件
                    Predicate predicate = cb.equal(root.get("author").as(String.class), author);
                    list.add(predicate);
                }

                return cb.and(list.toArray(new Predicate[]{}));
            }
        }, pageable);

        for (Article article : page.getContent()) {
            System.out.println(article);
        }
    }
}