一、基本介绍

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

  我们都知道,在使用持久化工具的时候,一般都有一个对象来操作数据库,在原生的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语句。
二、环境配置
1、maven配置

    首先需要spring相关架包,其实spring-data-jpa里面已经依赖了,如果你想用自己的版本则需要额外引入spring相关包.JPA实现还都是hibernate去实现的,所以还需要hibernate相关包.mysql就更不用说了.

    <!--JPA start-->
      <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-jpa</artifactId>
        <version>1.10.4.RELEASE</version>
      </dependency>
    <!--JPA end-->
​
    <!--hibernate start-->
    <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>
    <!--hibernate end-->
​
    <!--mysql start-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>${mysql.version}</version>
    </dependency>
    <!--mysql end-->

2、整合Spring

整合Spring主要有以下几点要注意:

数据源配置  
JPA提供者,JPA属性配置  
事务配置  
jpa:repositories 配置

具体如下代码:

 <!-- 加载数据库配置文件 -->
    <context:property-placeholder location="classpath:config.properties"/>
<!--配置数据库连接池Druid-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!-- 数据库基本信息配置 -->
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="driverClassName" value="${jdbc.driver}" />
        <property name="filters" value="${filters}" />
        <!-- 最大并发连接数 -->
        <property name="maxActive" value="${maxActive}" />
        <!-- 初始化连接数量 -->
        <property name="initialSize" value="${initialSize}" />
        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="${maxWait}" />
        <!-- 最小空闲连接数 -->
        <property name="minIdle" value="${minIdle}" />
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" />
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" />
        <property name="validationQuery" value="SELECT 1" />
        <property name="testWhileIdle" value="${testWhileIdle}" />
        <property name="testOnBorrow" value="${testOnBorrow}" />
        <property name="testOnReturn" value="${testOnReturn}" />
        <property name="maxOpenPreparedStatements" value="${maxOpenPreparedStatements}" />
        <!-- 打开removeAbandoned功能 -->
        <property name="removeAbandoned" value="${removeAbandoned}" />
        <!-- 1800秒,也就是30分钟 -->
        <property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}" />
        <!-- 关闭abanded连接时输出错误日志 -->
        <property name="logAbandoned" value="${logAbandoned}" />
    </bean>
​
    <!--第二步-->
    <!--定义实体的工厂bean-->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--实体类位置-->
        <property name="packagesToScan" value="cn.mrdear.entity"/>
        <!--持久化单元名-->
        <property name="persistenceUnitName" value="TestJPA" />
        <!--JPA提供者-->
        <property name="persistenceProviderClass" value="org.hibernate.ejb.HibernatePersistence"/>
        <!--JPA属性-->
        <property name="jpaProperties">
            <props>
                <!--配置方言-->
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
                <!--激活查询日志功能-->
                <prop key="hibernate.show_sql">false</prop>
                <!--优雅地输出Sql-->
                <prop key="hibernate.format_sql">false</prop>
                <!--添加一条解释型标注-->
                <prop key="hibernate.use_sql_comments">false</prop>
                <!--配置如何根据java模型生成数据库表结构,常用update,validate-->
                <prop key="hibernate.hbm2ddl.auto">none</prop>
            </props>
        </property>
    </bean>
​
    <!--第三步-->
    <!--定义事务管理器-->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="dataSource" ref="dataSource"/>
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>
​
    <!--第四步-->
    <!--定义repository接口的存放目录-->
    <!--定义接口实现的后缀,通常用Impl-->
    <!--定义实体工厂的引用-->
    <!--定义事务管理器的引用-->
    <jpa:repositories base-package="cn.mrdear.repository"
                      repository-impl-postfix="Impl"
                      entity-manager-factory-ref="entityManagerFactory"
                      transaction-manager-ref="transactionManager"/>
​
    <!--第五步-->
    <!--声明采用注解的方式申明事务-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

3、创建实体类

实体类中常用注解:

 @Entity :声明这个类是一个实体类  
 @Table:指定映射到数据库的表格  
 @Id :映射到数据库表的主键属性,一个实体只能有一个属性被映射为主键  
 @GeneratedValue:主键的生成策略  
 @Column配置单列属性 

@Entity//标识该为一个实体
@Table(name = "user")//关联数据库中的user表
public class User {
    @Id//标识该属性为主键
    private Integer id;
    private String name;
    private String address;
    private String phone;
    //省略get和set
}

4、Repository接口

Repository: 最顶层的接口,是一个空接口,目的是为了统一所有的Repository的类型,且能让组件扫描时自动识别 
CrudRepository: Repository的子接口,提供CRUD 的功能。 
PagingAndSortingRepository:CrudRepository的子接口, 添加分页排序。 
JpaRepository: PagingAndSortingRepository的子接口,增加批量操作等。 
JpaSpecificationExecutor: 用来做复杂查询的接口。

由图来看,一般使用JpaRepository这个接口做查询即可.这个接口拥有如下方法:

delete删除或批量删除 
findOne查找单个 
findAll查找所有 
save保存单个或批量保存 
saveAndFlush保存并刷新到数据库

知道这些我们就可以创建repository 了:

//User表示该Repository与实体User关联,主键类型为Integer
public interface UserRepository extends JpaRepository<User,Integer> {
}

​这样就完成了一个基本Repository的创建,可以直接使用其中的方法,而不需要去写实现类。
5、测试

    关于测试这里,我把测试案例写到test文件夹的话,总是报实体类未被JPA管理,所以改写到java文件夹,具体原因未知.

    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring/applicationContext.xml");
        UserRepository userRepository = (UserRepository) applicationContext.getBean("userRepository");
        System.out.println(userRepository.findAll());
        System.out.println(userRepository.findOne(1));
        System.out.println(userRepository.findAll(new Sort(new Sort.Order(Sort.Direction.ASC,"id"))));
    }

三、CRUD操作
1、增加

    增加可以使用JpaRepository接口里面的save方法.查看源码可以发现实际上是使用了em.persist(entity)来使对象进入持久化状态,最后提交事务的时候再一起更新到数据库:

User user = new User();
user.setId(99);
user.setAddress("上海");
user.setName("张三");
user.setPhone("110");
//保存单个
userRepository.save(user);
//保存或更新
userRepository.saveAndFlush(user);
List<User> users = new ArrayList<>();
users.add(user);
//保存多个
userRepository.save(users);

    这里还可以批量插入,对于mysql支持INSERT user VALUES (20,'王二','111','111'),(21,'王二','111','111');类似这样的sql语句,具体实现就需要自己去写实现了,这样可以一次插入多条记录,效率很高.至于一次插入多少条就可以根据你的业务量来自己制定。

2、删除

    删除都是根据主键来删除的,区别就是多条sql和单条sql :

User user = new User();
user.setId(21);
user.setName("王二");
/**
 * 删除都是根据主键删除
 */
//删除单条,根据主键值
userRepository.delete(20);
//删除全部,先findALL查找出来,再一条一条删除,最后提交事务
userRepository.deleteAll();
//删除全部,一条sql
userRepository.deleteAllInBatch();
List<User> users = new ArrayList<>();
users.add(user);
//删除集合,一条一条删除
userRepository.delete(users);
//删除集合,一条sql,拼接or语句 如 id=1 or id=2
userRepository.deleteInBatch(users);

3、修改

    修改也是根据主键来更新的 :

User user = new User();
user.setId(1);
user.setName("张三");
/**
 * 更新也是根据主键来更新 update XX xx where id=1
 */
userRepository.saveAndFlush(user);

批量更新的话,就调用entityManager的merge函数来更新.

//首先在service层获取持久化管理器:
@PersistenceContext
private EntityManager em;
//批量更新方法,同理插入,删除也都可以如此做.
@Transactional
public void batchUpateCustom(List<User> users) {
    // TODO Auto-generated method stub
    for(int i = 0; i < users.size(); i++) { 
        em.merge(users.get(i)); 
        if(i % 30== 0) {
            em.flush(); 
            em.clear(); 
        } 
    }
}

4、简单查询

    单表查询,大部分都可以使用下面的方法解决,多表联合查询的话,下面方法就不是很实用,下一节分析多表查询。

1.使用JpaRepository方法

//查找全部
userRepository.findAll();
//分页查询全部,返回封装了分页信息
Page<User> pageInfo = userRepository.findAll(new PageRequest(1, 3, Sort.Direction.ASC,"id"));
//查找全部,并排序
userRepository.findAll(new Sort(new Sort.Order(Sort.Direction.ASC,"id")));
User user = new User();
user.setName("小红");
//条件查询,可以联合分页,排序
userRepository.findAll(Example.of(user));
//查询单个
userRepository.findOne(1);

2.解析方法名创建查询

    规则:find+全局修饰+By+实体的属性名称+限定词+连接词+ …(其它实体属性)+OrderBy+排序属性+排序方向。例如:

//分页查询出符合姓名的记录,同理Sort也可以直接加上
public List<User> findByName(String name, Pageable pageable);

全局修饰: Distinct, Top, First

关键词: IsNull, IsNotNull, Like, NotLike, Containing, In, NotIn,

IgnoreCase, Between, Equals, LessThan, GreaterThan, After, Before…

排序方向: Asc, Desc

连接词: And, Or

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 userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;

NotIn — 等价于 SQL 中的 “not in”,比如 findByUsernameNotIn(Collection userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;

嵌套实体:

主实体中子实体的名称+ _ +子实体的属性名称

List findByAddress_ZipCode(ZipCode zipCode)

表示查询所有 Address(地址)的zipCode(邮编)为指定值的所有Person(人员)
3.JPQL查询

    一个类似HQL的语法,在接口上使用@Query标识:

 @Query("select a from user a where a.id = ?1")
 public User findById(Long id);

使用@Modifying标识修改

 @Modifying
 @Query("update User a set a.name = ?1 where a.id < ?2")
 public int updateName(String name, Long id);

携带分页信息:

@Query("select u from User u where u.name=?1")
public List<User> findByName(String name, Pageable pageable);

    除此之外也可以使用原生sql,只需要@Query(nativeQuery=true)标识即可.

创建查询顺序:

Spring Data JPA 在为接口创建代理对象时,如果发现同时存在多种上述情况可用,它该优先采用哪种策略呢?为此, 提供了 query-lookup-strategy 属性,用以指定查找的顺序。它有如下三个取值:

create — 通过解析方法名字来创建查询。即使有符合的命名查询,或者方法通过 @Query 指定的查询语句,都将会被忽略。
create-if-not-found — 如果方法通过 @Query 指定了查询语句,则使用该语句实现查询;如果没有,则查找是否定义了符合条件的命名查询,如果找到,则使用该命名查询;如果两者都没有找到,则通过解析方法名字来创建查询。这是 query-lookup-strategy 属性的默认值。
use-declared-query — 如果方法通过 @Query 指定了查询语句,则使用该语句实现查询;如果没有,则查找是否定义了符合条件的命名查询,如果找到,则使用该命名查询;如果两者都没有找到,则抛出异常。

4.计数

    计数就直接使用JpaRepository的count方法:

//查找总数量
userRepository.count();
User user = new User();
user.setName("小红");
//条件计数
userRepository.count(Example.of(user));

5.判断是否存在

    计数就直接使用JpaRepository的exists方法:

//根据主键判断是否存在
userRepository.exists(1);
User user = new User();
user.setName("小红");
//根据条件判断是否存在
userRepository.exists(Example.of(user));

6.自定义查询

    首先自定义一个接口,用于定义自定义方法,如UserRepositoryCustom,然后让UserRepository实现该接口,这样的话就可以使用其中的方法。然后写UserRepositoryImpl实现UserRepositoryCustom接口。

    最后设置jpa:repositories的repository-impl-postfix="Impl",这样的话JPA会查找自定义实现类命名规则,这样的话JPA在相应UserRepository包下面查找实现类,找到则会使用其中的实现方法,而不去自己实现。具体可以看项目demo,或者下一节的复杂查询。

5、复杂查询
1.使用CriteriaBuilder构建JPQL

    在UserRepositoryImpl中使用CriteriaBuilder实现根据id查询,下面是代码:

public void findById(Integer id){
    //select u from User u where u.id = 1
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<User> cq = cb.createQuery(User.class);
    Root<User> root = cq.from(User.class); //from User
    cq.select(root); //select * from User
    javax.persistence.criteria.Predicate pre = cb.equal(root.get("id").as(Integer.class),id);//id=1
    cq.where(pre);//where id=1
    Query query = entityManager.createQuery(cq);//select u from User u where u.id = 1
​
    System.out.println(query.getResultList());
}

缺点:

代码量多
 不易维护
 条件复杂的话,则会显得很混乱.

2.使用JpaSpecificationExecutor查询

    该接口有如下方法,里面传入条件都是Specification,该接口会返回一个Predicate条件集合,因此就可以在这里封装:

public interface JpaSpecificationExecutor<T> {
    T findOne(Specification<T> spec);
    List<T> findAll(Specification<T> spec);
    Page<T> findAll(Specification<T> spec, Pageable pageable);
    List<T> findAll(Specification<T> spec, Sort sort);
    long count(Specification<T> spec);
}

1.构造过滤条件集合

    Operator枚举类里面的operator属性为了构建原生sql使用:

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
​
import java.io.Serializable;
​
/**
 * 筛选
 */
public class Filter implements Serializable {
​
    private static final long serialVersionUID = -8712382358441065075L;
​
    /**
     * 运算符
     */
    public enum Operator {
​
        /** 等于 */
        eq(" = "),
​
        /** 不等于 */
        ne(" != "),
​
        /** 大于 */
        gt(" > "),
​
        /** 小于 */
        lt(" < "),
​
        /** 大于等于 */
        ge(" >= "),
​
        /** 小于等于 */
        le(" <= "),
​
        /** 类似 */
        like(" like "),
​
        /** 包含 */
        in(" in "),
​
        /** 为Null */
        isNull(" is NULL "),
​
        /** 不为Null */
        isNotNull(" is not NULL ");
        Operator(String operator) {
            this.operator = operator;
        }
​
        private String operator;
​
        public String getOperator() {
            return operator;
        }
​
        public void setOperator(String operator) {
            this.operator = operator;
        }
    }
​
    /** 默认是否忽略大小写 */
    private static final boolean DEFAULT_IGNORE_CASE = false;
​
    /** 属性 */
    private String property;
​
    /** 运算符 */
    private Filter.Operator operator;
​
    /** 值 */
    private Object value;
​
    /** 是否忽略大小写 */
    private Boolean ignoreCase = DEFAULT_IGNORE_CASE;
​
    /**
     * 构造方法
     */
    public Filter() {
    }
​
    /**
     * 构造方法
     *
     * @param property
     *            属性
     * @param operator
     *            运算符
     * @param value
     *            值
     */
    public Filter(String property, Filter.Operator operator, Object value) {
        this.property = property;
        this.operator = operator;
        this.value = value;
    }
​
    /**
     * 构造方法
     *
     * @param property
     *            属性
     * @param operator
     *            运算符
     * @param value
     *            值
     * @param ignoreCase
     *            忽略大小写
     */
    public Filter(String property, Filter.Operator operator, Object value, boolean ignoreCase) {
        this.property = property;
        this.operator = operator;
        this.value = value;
        this.ignoreCase = ignoreCase;
    }
​
    /**
     * 返回等于筛选
     *
     * @param property
     *            属性
     * @param value
     *            值
     * @return 等于筛选
     */
    public static Filter eq(String property, Object value) {
        return new Filter(property, Filter.Operator.eq, value);
    }
​
    /**
     * 返回等于筛选
     *
     * @param property
     *            属性
     * @param value
     *            值
     * @param ignoreCase
     *            忽略大小写
     * @return 等于筛选
     */
    public static Filter eq(String property, Object value, boolean ignoreCase) {
        return new Filter(property, Filter.Operator.eq, value, ignoreCase);
    }
​
    /**
     * 返回不等于筛选
     *
     * @param property
     *            属性
     * @param value
     *            值
     * @return 不等于筛选
     */
    public static Filter ne(String property, Object value) {
        return new Filter(property, Filter.Operator.ne, value);
    }
​
    /**
     * 返回不等于筛选
     *
     * @param property
     *            属性
     * @param value
     *            值
     * @param ignoreCase
     *            忽略大小写
     * @return 不等于筛选
     */
    public static Filter ne(String property, Object value, boolean ignoreCase) {
        return new Filter(property, Filter.Operator.ne, value, ignoreCase);
    }
​
    /**
     * 返回大于筛选
     *
     * @param property
     *            属性
     * @param value
     *            值
     * @return 大于筛选
     */
    public static Filter gt(String property, Object value) {
        return new Filter(property, Filter.Operator.gt, value);
    }
​
    /**
     * 返回小于筛选
     *
     * @param property
     *            属性
     * @param value
     *            值
     * @return 小于筛选
     */
    public static Filter lt(String property, Object value) {
        return new Filter(property, Filter.Operator.lt, value);
    }
​
    /**
     * 返回大于等于筛选
     *
     * @param property
     *            属性
     * @param value
     *            值
     * @return 大于等于筛选
     */
    public static Filter ge(String property, Object value) {
        return new Filter(property, Filter.Operator.ge, value);
    }
​
    /**
     * 返回小于等于筛选
     *
     * @param property
     *            属性
     * @param value
     *            值
     * @return 小于等于筛选
     */
    public static Filter le(String property, Object value) {
        return new Filter(property, Filter.Operator.le, value);
    }
​
    /**
     * 返回相似筛选
     *
     * @param property
     *            属性
     * @param value
     *            值
     * @return 相似筛选
     */
    public static Filter like(String property, Object value) {
        return new Filter(property, Filter.Operator.like, value);
    }
​
    /**
     * 返回包含筛选
     *
     * @param property
     *            属性
     * @param value
     *            值
     * @return 包含筛选
     */
    public static Filter in(String property, Object value) {
        return new Filter(property, Filter.Operator.in, value);
    }
​
    /**
     * 返回为Null筛选
     *
     * @param property
     *            属性
     * @return 为Null筛选
     */
    public static Filter isNull(String property) {
        return new Filter(property, Filter.Operator.isNull, null);
    }
​
    /**
     * 返回不为Null筛选
     *
     * @param property
     *            属性
     * @return 不为Null筛选
     */
    public static Filter isNotNull(String property) {
        return new Filter(property, Filter.Operator.isNotNull, null);
    }
​
    /**
     * 返回忽略大小写筛选
     *
     * @return 忽略大小写筛选
     */
    public Filter ignoreCase() {
        this.ignoreCase = true;
        return this;
    }
​
    /**
     * 获取属性
     *
     * @return 属性
     */
    public String getProperty() {
        return property;
    }
​
    /**
     * 设置属性
     *
     * @param property
     *            属性
     */
    public void setProperty(String property) {
        this.property = property;
    }
​
    /**
     * 获取运算符
     *
     * @return 运算符
     */
    public Filter.Operator getOperator() {
        return operator;
    }
​
    /**
     * 设置运算符
     *
     * @param operator
     *            运算符
     */
    public void setOperator(Filter.Operator operator) {
        this.operator = operator;
    }
​
    /**
     * 获取值
     *
     * @return 值
     */
    public Object getValue() {
        return value;
    }
​
    /**
     * 设置值
     *
     * @param value
     *            值
     */
    public void setValue(Object value) {
        this.value = value;
    }
​
    /**
     * 获取是否忽略大小写
     *
     * @return 是否忽略大小写
     */
    public Boolean getIgnoreCase() {
        return ignoreCase;
    }
​
    /**
     * 设置是否忽略大小写
     *
     * @param ignoreCase
     *            是否忽略大小写
     */
    public void setIgnoreCase(Boolean ignoreCase) {
        this.ignoreCase = ignoreCase;
    }
​
    /**
     * 重写equals方法
     *
     * @param obj
     *            对象
     * @return 是否相等
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        if (this == obj) {
            return true;
        }
        Filter other = (Filter) obj;
        return new EqualsBuilder().append(getProperty(), other.getProperty()).append(getOperator(), other.getOperator()).append(getValue(), other.getValue()).isEquals();
    }
​
    /**
     * 重写hashCode方法
     *
     * @return HashCode
     */
    @Override
    public int hashCode() {
        return new HashCodeBuilder(17, 37).append(getProperty()).append(getOperator()).append(getValue()).toHashCode();
    }
}

2.构造排序字段

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import java.io.Serializable;
/**
 * 排序
 *
 * @author copy from shopxx
 */
public class Order implements Serializable {
​
    private static final long serialVersionUID = -3078342809727773232L;
​
    /**
     * 方向
     */
    public enum Direction {
​
        /** 递增 */
        asc,
​
        /** 递减 */
        desc
    }
​
    /** 默认方向 */
    private static final Order.Direction DEFAULT_DIRECTION = Order.Direction.desc;
​
    /** 属性 */
    private String property;
​
    /** 方向 */
    private Order.Direction direction = DEFAULT_DIRECTION;
​
    @Override
    public String toString() {
        return property+" " + direction.name();
    }
​
    /**
     * 构造方法
     */
    public Order() {
    }
​
    /**
     * 构造方法
     *
     * @param property
     *            属性
     * @param direction
     *            方向
     */
    public Order(String property, Order.Direction direction) {
        this.property = property;
        this.direction = direction;
    }
​
    /**
     * 返回递增排序
     *
     * @param property
     *            属性
     * @return 递增排序
     */
    public static Order asc(String property) {
        return new Order(property, Order.Direction.asc);
    }
​
    /**
     * 返回递减排序
     *
     * @param property
     *            属性
     * @return 递减排序
     */
    public static Order desc(String property) {
        return new Order(property, Order.Direction.desc);
    }
​
    /**
     * 获取属性
     *
     * @return 属性
     */
    public String getProperty() {
        return property;
    }
​
    /**
     * 设置属性
     *
     * @param property
     *            属性
     */
    public void setProperty(String property) {
        this.property = property;
    }
​
    /**
     * 获取方向
     *
     * @return 方向
     */
    public Order.Direction getDirection() {
        return direction;
    }
​
    /**
     * 设置方向
     *
     * @param direction
     *            方向
     */
    public void setDirection(Order.Direction direction) {
        this.direction = direction;
    }
​
    /**
     * 重写equals方法
     *
     * @param obj
     *            对象
     * @return 是否相等
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        if (this == obj) {
            return true;
        }
        Order other = (Order) obj;
        return new EqualsBuilder().append(getProperty(), other.getProperty()).append(getDirection(), other.getDirection()).isEquals();
    }
​
    /**
     * 重写hashCode方法
     *
     * @return HashCode
     */
    @Override
    public int hashCode() {
        return new HashCodeBuilder(17, 37).append(getProperty()).append(getDirection()).toHashCode();
    }
}

3.查询语句生成

1.基本框架

/**
 * 封装查询条件的实体
 */
public class QueryParams<T> implements Specification<T> {
​
    /** 属性分隔符 */
    private static final String PROPERTY_SEPARATOR = ".";
    /**
     * and条件
     */
    private List<Filter> andFilters = new ArrayList<>();
    /**
     * or条件
     */
    private List<Filter> orFilters = new ArrayList<>();
    /**
     * 排序属性
     */
    private List<Order> orders = new ArrayList<>();
        /**
     * 获取Path
     *
     * @param path
     *            Path
     * @param propertyPath
     *            属性路径
     * @return Path
     */
    @SuppressWarnings("unchecked")
    private <X> Path<X> getPath(Path<?> path, String propertyPath) {
        if (path == null || StringUtils.isEmpty(propertyPath)) {
            return (Path<X>) path;
        }
        String property = StringUtils.substringBefore(propertyPath, PROPERTY_SEPARATOR);
        return getPath(path.get(property), StringUtils.substringAfter(propertyPath, PROPERTY_SEPARATOR));
    }
}

2.分析and条件

/**
 * 转换为Predicate
 */
@SuppressWarnings("unchecked")
private Predicate toAndPredicate(Root<T> root,CriteriaBuilder criteriaBuilder) {
    Predicate restrictions = criteriaBuilder.conjunction();
    if (root == null || CollectionUtils.isEmpty(andFilters)) {
        return restrictions;
    }
    for (Filter filter : andFilters) {
        if (filter == null) {
            continue;
        }
        String property = filter.getProperty();
        Filter.Operator operator = filter.getOperator();
        Object value = filter.getValue();
        Boolean ignoreCase = filter.getIgnoreCase();
        Path<?> path = getPath(root, property);
        if (path == null) {
            continue;
        }
        //根据运算符生成相应条件
        switch (operator) {
            case eq:
                if (value != null) {
                    if (BooleanUtils.isTrue(ignoreCase) && String.class.isAssignableFrom(path.getJavaType()) && value instanceof String) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.equal(criteriaBuilder.lower((Path<String>) path), ((String) value).toLowerCase()));
                    } else {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.equal(path, value));
                    }
                } else {
                    restrictions = criteriaBuilder.and(restrictions, path.isNull());
                }
                break;
            case ne:
                if (value != null) {
                    if (BooleanUtils.isTrue(ignoreCase) && String.class.isAssignableFrom(path.getJavaType()) && value instanceof String) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.notEqual(criteriaBuilder.lower((Path<String>) path), ((String) value).toLowerCase()));
                    } else {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.notEqual(path, value));
                    }
                } else {
                    restrictions = criteriaBuilder.and(restrictions, path.isNotNull());
                }
                break;
            case gt:
                if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
                    restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.gt((Path<Number>) path, (Number) value));
                }
                break;
            case lt:
                if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
                    restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.lt((Path<Number>) path, (Number) value));
                }
                break;
            case ge:
                if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
                    restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.ge((Path<Number>) path, (Number) value));
                }
                break;
            case le:
                if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
                    restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.le((Path<Number>) path, (Number) value));
                }
                break;
            case like:
                if (String.class.isAssignableFrom(path.getJavaType()) && value instanceof String) {
                    if (BooleanUtils.isTrue(ignoreCase)) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.like(criteriaBuilder.lower((Path<String>) path), ((String) value).toLowerCase()));
                    } else {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.like((Path<String>) path, (String) value));
                    }
                }
                break;
            case in:
                restrictions = criteriaBuilder.and(restrictions, path.in(value));
                break;
            case isNull:
                restrictions = criteriaBuilder.and(restrictions, path.isNull());
                break;
            case isNotNull:
                restrictions = criteriaBuilder.and(restrictions, path.isNotNull());
                break;
        }
    }
    return restrictions;
}

3.分析or条件

    把and中的and改为or即可:

/**
 * 转换为Predicate
 */
@SuppressWarnings("unchecked")
private Predicate toOrPredicate(Root<T> root,CriteriaBuilder criteriaBuilder) {
    Predicate restrictions = criteriaBuilder.disjunction();
    if (root == null || CollectionUtils.isEmpty(andFilters)) {
        return restrictions;
    }
    for (Filter filter : orFilters) {
        if (filter == null) {
            continue;
        }
        String property = filter.getProperty();
        Filter.Operator operator = filter.getOperator();
        Object value = filter.getValue();
        Boolean ignoreCase = filter.getIgnoreCase();
        Path<?> path = getPath(root, property);
        if (path == null) {
            continue;
        }
        switch (operator) {
            case eq:
                if (value != null) {
                    if (BooleanUtils.isTrue(ignoreCase) && String.class.isAssignableFrom(path.getJavaType()) && value instanceof String) {
                        restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.equal(criteriaBuilder.lower((Path<String>) path), ((String) value).toLowerCase()));
                    } else {
                        restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.equal(path, value));
                    }
                } else {
                    restrictions = criteriaBuilder.or(restrictions, path.isNull());
                }
                break;
            case ne:
                if (value != null) {
                    if (BooleanUtils.isTrue(ignoreCase) && String.class.isAssignableFrom(path.getJavaType()) && value instanceof String) {
                        restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.notEqual(criteriaBuilder.lower((Path<String>) path), ((String) value).toLowerCase()));
                    } else {
                        restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.notEqual(path, value));
                    }
                } else {
                    restrictions = criteriaBuilder.or(restrictions, path.isNotNull());
                }
                break;
            case gt:
                if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
                    restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.gt((Path<Number>) path, (Number) value));
                }
                break;
            case lt:
                if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
                    restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.lt((Path<Number>) path, (Number) value));
                }
                break;
            case ge:
                if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
                    restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.ge((Path<Number>) path, (Number) value));
                }
                break;
            case le:
                if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
                    restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.le((Path<Number>) path, (Number) value));
                }
                break;
            case like:
                if (String.class.isAssignableFrom(path.getJavaType()) && value instanceof String) {
                    if (BooleanUtils.isTrue(ignoreCase)) {
                        restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.like(criteriaBuilder.lower((Path<String>) path), ((String) value).toLowerCase()));
                    } else {
                        restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.like((Path<String>) path, (String) value));
                    }
                }
                break;
            case in:
                restrictions = criteriaBuilder.or(restrictions, path.in(value));
                break;
            case isNull:
                restrictions = criteriaBuilder.or(restrictions, path.isNull());
                break;
            case isNotNull:
                restrictions = criteriaBuilder.or(restrictions, path.isNotNull());
                break;
        }
    }
    return restrictions;
}

4.分析排序条件

/**
 * 转换为Order
 */
private List<javax.persistence.criteria.Order> toOrders(Root<T> root,CriteriaBuilder criteriaBuilder) {
    List<javax.persistence.criteria.Order> orderList = new ArrayList<javax.persistence.criteria.Order>();
    if (root == null || CollectionUtils.isEmpty(orders)) {
        return orderList;
    }
    for (Order order : orders) {
        if (order == null) {
            continue;
        }
        String property = order.getProperty();
        Order.Direction direction = order.getDirection();
        Path<?> path = getPath(root, property);
        if (path == null || direction == null) {
            continue;
        }
        switch (direction) {
            case asc:
                orderList.add(criteriaBuilder.asc(path));
                break;
            case desc:
                orderList.add(criteriaBuilder.desc(path));
                break;
        }
    }
    return orderList;
}

最后在toPredicate方法中构造最终条件:

/**
 * 生成条件的
 * @param root 该对象的封装
 * @param query 查询构建器
 * @param cb 构建器
 * @return 条件集合
 */
@Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
    Predicate restrictions = cb.and(toAndPredicate(root,cb));
    restrictions = cb.and(restrictions,toOrPredicate(root,cb));
    query.orderBy(toOrders(root,cb));
    return restrictions;
}

加上方便的链式调用方法:

/**
 * 添加一个and条件
 * @param filter 该条件
 * @return 链式调用
 */
public  QueryParams and(Filter filter){
    this.andFilters.add(filter);
    return this;
}
/**
 * 添加多个and条件
 * @param filter 该条件
 * @return 链式调用
 */
public  QueryParams and(Filter ...filter){
    this.andFilters.addAll(Arrays.asList(filter));
    return this;
}
/**
 * 添加一个or条件
 * @param filter 该条件
 * @return 链式调用
 */
public  QueryParams or(Filter filter){
    this.orFilters.add(filter);
    return this;
}
/**
 * 添加多个or条件
 * @param filter 该条件
 * @return 链式调用
 */
public  QueryParams or(Filter ...filter){
    this.orFilters.addAll(Arrays.asList(filter));
    return this;
}
/**
 * 升序字段
 * @param property 该字段对应变量名
 * @return 链式调用
 */
public  QueryParams orderASC(String property){
    this.orders.add(Order.asc(property));
    return this;
}
/**
 * 降序字段
 * @param property 该字段对应变量名
 * @return 链式调用
 */
public  QueryParams orderDESC(String property){
    this.orders.add(Order.desc(property));
    return this;
}
​
/**
 * 清除所有条件
 * @return 该实例
 */
public QueryParams clearAll(){
    if (!this.andFilters.isEmpty()) this.andFilters.clear();
    if (!this.orFilters.isEmpty()) this.orFilters.clear();
    if (!this.orders.isEmpty()) this.orders.clear();
    return this;
}
/**
 * 清除and条件
 * @return 该实例
 */
public QueryParams clearAnd(){
    if (!this.andFilters.isEmpty()) this.andFilters.clear();
    return this;
}
/**
 * 清除or条件
 * @return 该实例
 */
public QueryParams clearOr(){
    if (!this.orFilters.isEmpty()) this.andFilters.clear();
    return this;
}
/**
 * 清除order条件
 * @return 该实例
 */
public QueryParams clearOrder(){
    if (!this.orders.isEmpty()) this.orders.clear();
    return this;
}
//省略get和set方法

4.测试

    首先让PcardOrderRepository接口继承加上JpaSpecificationExecutor:

public interface PcardOrderRepository extends JpaRepository<PcardOrder,String>,PcardOrderRepositoryCustom,JpaSpecificationExecutor<PcardOrder> {
}

编写测试代码,这个使用的是CriteriaBuilder构建查询的,所以查询字段都是JPQL字段,并不是原生sql:

QueryParams<PcardOrder> queryParams = new QueryParams<>();
//使用Specification条件查询,使用JPQL字段查询
queryParams
        .and(Filter.eq("acctId","0014779934917371041"),Filter.ne("orderAmt",0L),
                Filter.eq("orderRespCd","00"))
        .or(Filter.eq("orderTypeId","A003"),Filter.eq("orderTypeId","A007"),
                Filter.eq("orderTypeId","A021"),Filter.eq("orderTypeId","A018"))
        .orderDESC("createTime");
​
Page<PcardOrder> JPQLlist = pcardOrderRepository.findAll(queryParams,new PageRequest(0,2));
​
//构造出来的条件
where
1=1
and pcardorder0_.acct_id=?
and pcardorder0_.order_amt<>0
and pcardorder0_.order_resp_cd=?
and (
    0=1
    or pcardorder0_.order_type_id=?
    or pcardorder0_.order_type_id=?
    or pcardorder0_.order_type_id=?
    or pcardorder0_.order_type_id=?
)
order by
pcardorder0_.create_time desc limit ?

3.原生sql查询

    还是利用上面的Filter,具体还是遍历+拼接,示例中我卸载了公共方法中,需要使用的Impl直接extends即可。

1.解析条件

    解析条件实际上就是拼接sql,代码很简单:

/**
 * 公共方法的repository
 */
@NoRepositoryBean
public class BaseRepository {
​
    private static Logger logger = LoggerFactory.getLogger(BaseRepository.class);
​
    /**
     * 分析查询参数,并且合并到sql语句中
     * @param sql JPQL查询语句
     * @param params 查询参数
     * @return 参数对应的value
     */
    @SuppressWarnings("Unchecked")
    protected List<Object> analysisQueryParams(StringBuilder sql, QueryParams<?> params){
        List<String> strList = new ArrayList<>();
        List<Object> valueList = new ArrayList<>();
        int i = 1;
        //分析or条件
        for (Filter filter : params.getOrFilters()) {
            if (filter.getValue() != null){
                strList.add(filter.getProperty()+" " + filter.getOperator().getOperator()+" ?" + (i++));
                valueList.add(filter.getValue());
            }else {
                strList.add(filter.getProperty()+" " + filter.getOperator().getOperator()+" ");
            }
        }
        if (!strList.isEmpty()){
            sql.append(" and ").append("( ").append(StringUtils.join(strList," or ")).append(" )");
        }
        strList.clear();
        //分析and条件
        for (Filter filter : params.getAndFilters()) {
            if (filter.getValue() != null){
                strList.add(filter.getProperty()+" " + filter.getOperator().getOperator()+" ?" + (i++));
                valueList.add(filter.getValue());
            }else {
                strList.add(filter.getProperty()+" " + filter.getOperator().getOperator()+" ");
            }
        }
        sql.append(" and ").append(StringUtils.join(strList," and "));
        //分析排序字段
        if (!params.getOrders().isEmpty()){
            sql.append(" order by ");
            sql.append(StringUtils.join(params.getOrders(),","));
        }
        logger.debug("解析后的sql:"+sql.toString());
        logger.debug("对应的值为:"+valueList);
        return valueList;
    }
​
}

2.测试

    在自定义接口中加入方法:

public interface PcardOrderRepositoryCustom {
    List findByQueryParam(QueryParams queryParams, Pageable pageable);
}

然后实现类中需要写部分sql

@NoRepositoryBean
public class PcardOrderRepositoryImpl extends BaseRepository implements PcardOrderRepositoryCustom {
​
    @PersistenceContext
    private EntityManager entityManager;
    @Override
    public List findByQueryParam(QueryParams queryParams, Pageable pageable) {
        StringBuilder sql = new StringBuilder("select * from tbl_pcard_order where 1=1 ");
        List values = analysisQueryParams(sql,queryParams);
        Query query = entityManager.createNativeQuery(sql.toString());
        for (int i = 0; i < values.size(); i++) {
            query.setParameter(i+1,values.get(i));
        }
        query.setFirstResult(pageable.getOffset());
        query.setMaxResults(pageable.getPageSize());
        return query.getResultList();
    }
}

测试代码:

//使用原生sql查询,注意这里使用原生sql字段,并非JPQL字段,
//本质是根据BaseRepository.analysisQueryParams 来拼接条件,可以根据自己的需求更改
queryParams.clearAll()
        .and(Filter.eq("acct_id","0014779934917371041"),Filter.ne("order_amt",0L),
                Filter.eq("order_resp_cd","00"))
        .or(Filter.eq("order_type_id","A003"),Filter.eq("order_type_id","A007"),
                Filter.eq("order_type_id","A021"),Filter.eq("order_type_id","A018"))
        .orderDESC("create_time");
List nativeSqlList = pcardOrderRepository.findByQueryParam(queryParams,new PageRequest(0,2));
//构造出来的sql
where
1=1 
and (
    order_type_id  =  ?
    or order_type_id  =  ?
    or order_type_id  =  ?
    or order_type_id  =  ?
)
and acct_id  =  ?
and order_amt  !=  ?
and order_resp_cd  =  ?
order by
create_time desc limit ?

4.使用原生sql查询出Map集合

    使用原生sql进行表关联查询,返回值一般都用List:

public List<Object[]> findById(int id) {
    String sql = "select u.*,c.* from user u,commont c where u.id = c.id and id=?1";
    Query query = entityManager.createNativeQuery(sql);
    query.setParameter(1,id);
    return query.getResultList();
}

那么就要改进,使其返回Map:

public List findById (int id) {
    String sql = "select u.*,c.* from user u,commont c where u.id = c.id and id=?1";
    Query query = entityManager.createNativeQuery(sql);
    query.setParameter(1,id);
    //转换为Map集合
    query.unwrap(org.hibernate.SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
    return query.getResultList();
}
​
//实际上返回的是一个List<Map>集合

这样的返回值是一个List类型,取出的时候直接根据键值取即可。
四、项目实战
1、基本配置

    参考《 Spring Boot整理——Thymeleaf模板》的项目,首先修改build.gradle的配置如下:

// buildscript 代码块中脚本优先执行
buildscript {
​
    // ext 用于定义动态属性
    ext {
        springBootVersion = '1.5.2.RELEASE'
    }

    // 自定义  Thymeleaf 和 Thymeleaf Layout Dialect 的版本
    ext['thymeleaf.version'] = '3.0.3.RELEASE'
    ext['thymeleaf-layout-dialect.version'] = '2.2.0'

    // 自定义  Hibernate 的版本
    ext['hibernate.version'] = '5.2.8.Final'

    // 使用了 Maven 的中央仓库(你也可以指定其他仓库)
    repositories {
        //mavenCentral()
        maven {
            url 'http://maven.aliyun.com/nexus/content/groups/public/'
        }
    }

    // 依赖关系
    dependencies {
        // classpath 声明说明了在执行其余的脚本时,ClassLoader 可以使用这些依赖项
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}
​
// 使用插件
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
​
// 打包的类型为 jar,并指定了生成的打包的文件名称和版本
jar {
    baseName = 'jpa-in-action'
    version = '1.0.0'
}
​
// 指定编译 .java 文件的 JDK 版本
sourceCompatibility = 1.8
​
// 默认使用了 Maven 的中央仓库。这里改用自定义的镜像库
repositories {
    //mavenCentral()
    maven {
        url 'http://maven.aliyun.com/nexus/content/groups/public/'
    }
}
​
// 依赖关系
dependencies {
    // 该依赖对于编译发行是必须的
    compile('org.springframework.boot:spring-boot-starter-web')

    // 添加 Thymeleaf 的依赖
    compile('org.springframework.boot:spring-boot-starter-thymeleaf')
​
    // 添加 Spring Data JPA 的依赖
    compile('org.springframework.boot:spring-boot-starter-data-jpa')

    // 添加 MySQL连接驱动 的依赖
    compile('mysql:mysql-connector-java:6.0.5')
​
    // 该依赖对于编译测试是必须的,默认包含编译产品依赖和编译时依
    testCompile('org.springframework.boot:spring-boot-starter-test')

    //添加H2的依赖
    runtime('com.h2database:h2:1.4.193')
}

然后application.properties为:

# THYMELEAF 
spring.thymeleaf.encoding=UTF-8
# 热部署静态文件
spring.thymeleaf.cache=false
# 使用HTML5标准
spring.thymeleaf.mode=HTML5
​
#使用H2 控制台   访问页面http://localhost:8080/h2-console/,
spring.h2.console.enabled=true
​
# DataSource 测试时可以先不用mysql数据库
#spring.datasource.url=jdbc:mysql://localhost/blog?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC 
#spring.datasource.username=root
#spring.datasource.password=123456
#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
​
# JPA
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto=create-drop

注意:在测试阶段可以使用H2数据库,其管理页面如下(http://localhost:8080/h2-console/ ),默认的数据库为jdbc:h2:mem:testdb,如果启用mysql,把mysql注释放开即可。

2、实体改造

@Entity  // 实体
public class User implements Serializable{

    private static final long serialVersionUID = 1L;

    @Id  // 主键
    @GeneratedValue(strategy=GenerationType.IDENTITY) // 自增长策略
    private Long id; // 用户的唯一标识
​
    @Column(nullable = false) // 映射为字段,值不能为空
    private String name;

    @Column(nullable = false)
    private Integer age;
​
    @Override
    public String toString() {
        return String.format(
                "User[id=%d, name='%s', age='%d']",
                id, name, age);
    }
}

3、持久化层改造

接口改成如下内容,实现类删除,用spring 提供的

/**
 * 用户仓库.
 */
public interface UserRepository extends CrudRepository<User, Long>{
}

4、控制层改造

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired 
    private UserRepository userRepository;
​
    /**
     * 从 用户存储库 获取用户列表
     * @return
     */
    private List<User> getUserlist() {
        List<User> users = new ArrayList<>();
        for (User user : userRepository.findAll()) {
            users.add(user);
        }
        return users;
    }
​
    /**
     * 查询所用用户
     * @return
     */
    @GetMapping
    public ModelAndView list(Model model) {
        model.addAttribute("userList", getUserlist());
        model.addAttribute("title", "用户管理");
        return new ModelAndView("users/list", "userModel", model);
    }

    /**
     * 根据id查询用户
     * @param message
     * @return
     */
    @GetMapping("{id}")
    public ModelAndView view(@PathVariable("id") Long id, Model model) {
        User user = userRepository.findOne(id);
        model.addAttribute("user", user);
        model.addAttribute("title", "查看用户");
        return new ModelAndView("users/view", "userModel", model);
    }
​
    /**
     * 获取 form 表单页面
     * @param user
     * @return
     */
    @GetMapping("/form")
    public ModelAndView createForm(Model model) {
        model.addAttribute("user", new User(null, null));
        model.addAttribute("title", "创建用户");
        return new ModelAndView("users/form", "userModel", model);
    }
​
    /**
     * 新建用户
     * @param user
     * @param result
     * @param redirect
     * @return
     */
    @PostMapping
    public ModelAndView create(User user) {
        userRepository.save(user);
        return new ModelAndView("redirect:/users");
    }
​
    /**
     * 删除用户
     * @param id
     * @return
     */
    @GetMapping(value = "delete/{id}")
    public ModelAndView delete(@PathVariable("id") Long id, Model model) {
        userRepository.delete(id);
        model.addAttribute("userList", getUserlist());
        model.addAttribute("title", "删除用户");
        return new ModelAndView("users/list", "userModel", model);
    }
​
    /**
     * 修改用户
     * @param user
     * @return
     */
    @GetMapping(value = "modify/{id}")
    public ModelAndView modifyForm(@PathVariable("id") Long id, Model model) {
        User user = userRepository.findOne(id);
        model.addAttribute("user", user);
        model.addAttribute("title", "修改用户");
        return new ModelAndView("users/form", "userModel", model);
    }
​
}


关注微信服务号,手机看文章
关注微信服务号,手机看文章