1,概念

1)JPA

场景:整合第三方ORM框架,建立一种标准的方式ORM 访问数据库的统一。
现阶段JPA几乎都是接口,实现都是Hibernate在做。我们都知道,在使用持久化工具的时候,一般都有一个对象来操作数据库,在原生的Hibernate中叫做Session,在MyBatis中叫做SqlSession,而在JPA中叫做EntityManager通过这个对象来操作数据库。

对象关系映射(Object Relational Mapping,简称ORM)
通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中。

2)占位符

JPA里占位符:?1(必须按顺序传参)或 :userName (推荐,本地sql中出现的冒号需要通过双斜杠转义)。

3)JSR338(Java Specification Requests,JPA规范)

JSR 338主要定义了如何通过普通的Java domain进行关系数据库的操作及映射,概括如下:

1>Entity

  • 必须是顶级类
  • @Entity注解的类
  • 必须有一个无参的public 或 protected的构造方法
  • 不能是final类,且不能有final方法或final变量
  • 一个Entity类通常与数据库的一张表进行对应
  • 一个Entity实例表现为数据库的一条数据
  • 对Entity的操作即对数据库的操作
  • 生命周期包含初始、托管、释放、消亡

2>EntityManager

  • 对Entity持久化操作的主要对象
  • 通过EntityManagerFactory获取实例
  • 一个实例代表一个数据库连接
  • 每个线程拥有自己的EntityManager实例
  • 主要方法有persist、remove、merge、createQuery、find
  • 可使用@PersistenceContext注入

3>EntityManagerFactory

  • 创建EntityManager的工厂
  • EntityManagerFactory的创建成本很高,对于给定的数据库,系统应该只创建一个与之关联的Factory
  • 可使用@PersistenceUnit注入

4> EntityTransaction

  • 表示数据库事务,在增、删、改时使用
  • 可通过EntityManager.getTransaction()获取

5>Persistence Context

  • 维护一组托管状态的Entity实例
  • 与EntityManager是相关联的

6>Persistence Unit

  • 一组Entity及相关设置的逻辑单元
  • 定义创建EntityManagerFactory所需要的参数
  • 通过persistence.xml定义或者通过一个PersistenceUnitInfo对象

7>总结

通过Persistence Unit创建EntityManagerFactory,再通过EntityManagerFactory获取EntityManager。

2,EntityManager

EntityManager是JPA中用于增删改查的接口,它的作用是:对一组实体类(Entity Bean)与底层数据源(tabel或临时表)之间进行 O/R 映射的管理。

1)Entity生命周期

java 占位符替换 实现表达式求值 jpa 占位符_学习

状态名

描述

作为java对象存在

在实体管理器中存在

在数据库存在

New(瞬时对象)

尚未有id,还未和Persistence Context建立关联的对象

yes

no

no

Managed(持久化受管对象)

有id值,已经和Persistence Context建立了关联的对象

yes

yes

yes

Detached(游离态离线对象)

有id值,但没有和Persistence Context建立关联的对象

no

no

no

Removed(删除的对象)

有id值,尚且和Persistence Context有关联,但是已经准备好从数据库中删除

yes

yes

no

2)方法

1> 新增数据:em.persist(Object entity);

如果entity的主键不为空,而数据库没有该主键,会抛出异常;
如果entity的主键不为空,而数据库有该主键,且entity的其他字段与数据库不同,persist后不会更新数据库;

2> 根据主键查找数据:em.find(Class entityClass, Object primaryKey);

如果主键格式不正确,会抛出illegalArgumentException异常;
如果主键在数据库未找到数据返回null;

3>删除数据:em.remove(Object entity);

只能将Managed状态的Entity实例删除,由此Entity实例状态变为Removed;

4> 将Detached状态的Entity实例转至Managed状态:em.merge(T entity);

5> 将所有的Entity实例状态转至Detached状态:em.clear();

场景举例:

从数据查出对象A
B.list=A.list
修改B.list
save(B)

最后发现往往A在数据库种的值也跟着改变了,为避免这种情况,在每次save之前em.clear() 一下

6> 将所有Managed状态的Entity实例同步到数据库:em.flush();

7> 刷新获得最新Entity:em.refresh(Object entity);

加载Entity实例后,数据库该条数据被修改,refresh该实例,能得到数据库最新的修改,覆盖原来的Entity实例。

8> 获取Session对象:em.unwrap(Session.class)

3)托管方式

A. 容器托管(EntityManger && PersistenceContext)

@PersistenceContext 也可以用@Autowired注解。
    EntityManager em;

@PersistenceContext是jpa专有的注解(推荐),而@Autowired是spring自带的注释。
@AutowiredEntityManager不是线程安全的,当多个请求进来的时候,spring会创建多个线程,而@PersistenceContext就是用来为每个线程创建一个EntityManager的,而@Autowired就只创建了一个,为所有线程共用,有可能报错。
B. 应用托管(EntityManagerFactory && PersistenceUnit)

EntityManagerFactory 接口主要用来创建 EntityManager 实例。该接口约定了如下4个方法:
createEntityManager():用于创建实体管理器对象实例。
createEntityManager(Map map):用于创建实体管理器对象实例的重载方法,Map 参数用于提供 EntityManager 的属性。
isOpen():检查 EntityManagerFactory 是否处于打开状态。实体管理器工厂创建后一直处于打开状态,除非调用close()方法将其关闭。
close():关闭 EntityManagerFactory 。 EntityManagerFactory 关闭后将释放所有资源,isOpen()方法测试将返回 false,其它方法将不能调用,否则将导致IllegalStateException异常。

2,原理

1)Repository接口

java 占位符替换 实现表达式求值 jpa 占位符_spring_02

Repository

最顶层的接口,是一个空接口,目的是为了统一所有的Repository的类型,且能让组件扫描时自动识别。

CrudRepository

Repository的子接口,提供CRUD 的功能。

PagingAndSortingRepository

CrudRepository的子接口, 添加分页排序。

JpaRepository

PagingAndSortingRepository的子接口,增加批量操作等。

常用方法
delete删除或批量删除 
findOne查找单个 
findAllByIdAndName查找所有 
save保存单个或批量保存 
saveAndFlush保存并刷新到数据库
countByInvestUserId

方法

说明

dao.flush ()

同步持久上下文环境,即将持久上下文环境的所有未保存实体的状态信息保存到数据库中。

dao.refresh (Object entity)

用数据库实体记录的值更新实体对象的状态,即更新实例的属性值。

dao.clear ()

清除持久上下文环境,断开所有关联的实体。如果这时还有未提交的更新则会被撤消。

dao.contains (Object entity)

判断一个实例是否属于当前持久上下文环境管理的实体。

dao.isOpen ()

判断当前的实体管理器是否是打开状态。

dao.getTransaction ()

返回资源层的事务对象。EntityTransaction实例可以用于开始和提交多个事务。

dao.close ()

关闭实体管理器。之后若调用实体管理器实例的方法或其派生的查询对象的方法都将抛出 IllegalstateException 异常,除了getTransaction 和 isOpen方法(返回 false)。不过,当与实体管理器关联的事务处于活动状态时,调用 close 方法后持久上下文将仍处于被管理状态,直到事务完成。

JpaSpecificationExecutor

用来做复杂查询的接口。

2)启动过程

由于引入了starter-data-jpa,自动配置,spring启动时会实例化一个Repositories,然后扫描包,找出所有继承Repository的接口(除@NoRepositoryBean注解的类),遍历装配。为每一个接口创建相关实例。
SimpleJpaRespositry——用来进行默认的DAO操作,是所有Repository的默认实现
JpaRepositoryFactoryBean——装配bean,装载了动态代理Proxy,会以对应的DAO的beanName为key注册到DefaultListableBeanFactory中,在需要被注入的时候从这个bean中取出对应的动态代理Proxy注入给DAO
JdkDynamicAopProxy——动态代理对应的InvocationHandler,负责拦截DAO接口的所有的方法调用,然后做相应处理,比如findByUsername被调用的时候会先经过这个类的invoke方法

导入jpa,自动配置扫描包内所有继承了Repository接口的接口,为每一个接口实例一个代理类(SimpleJpaRepository),并为每个方法确定一个query策略,已经其他所需的bean,使用自定的repository时,是使用的代理类,经过一些列的拦截器后,选取一个query执行器JpaQueryExecution,然后创建Query对象,拼接sql,组装参数等DB操作,最后返回Query.getResultList()。

3,使用

1)maven配置以及相关数据库依赖配置

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <version>2.1.7.RELEASE</version>
</dependency>

2)实体类及注解

@Table(name=userinfoTab) 
@Entity 
public class Userinfo implements Serializable {
    @Id  //映射到数据库表的主键属性
    @GeneratedValue(strategy=GenerationType.AUTO)  //主键的生成策略(自增)
    private Long user_id;
    private String user_name;
    @Column(length = 255) //配置单列属性
    private String user_tel;
    @Transient
    private String new_time;
	...
}

@Entity

声明这个类是一个实体类,必写。

@Table(name=userinfoTab)

指定映射到数据库的表格。
name 表名
catalog 设置表所属的数据库目录
schema 设置表所属的模式

@Id

映射到数据库表的主键属性。
jpa对应的entity必须有id,标识并唯一确定entity。

在字段上添加@Id注解后(否则是getter级的),使用字段级访问,解放setter和getter方法做业务逻辑处理,推荐这种方式。

@idClass(复合主键)

a. 复合主键类

编写一个复合主键的类CustomerPK,代码如下:CustomerPK.java
作为符合主键类,要满足以下几点要求。

  1. 必须实现Serializable接口。
  2. 必须有默认的public无参数的构造方法。
  3. 必须覆盖equals和hashCode方法。

@Data自动生成构造函数: CustomerPK(),CustomerPK(String name, String email);set,get方法;hashCode() ,equals(Object obj) 方法。
equals方法用于判断两个对象是否相同,EntityManger通过find方法来查找Entity时,是根据equals的返回值来判断的。本例中,只有对象的name和email值完全相同时或同一个对象时则返回true,否则返回false。hashCode方法返回当前对象的哈希码,生成的hashCode相同的概率越小越好,算法可以进行优化。

import java.io.Serializable;
//复合主键对象
@Data
public class CustomerPK implements Serializable {

         private String email;
         private String name;
}

b. @IdClass注解

通过@IdClass注释在实体中标注复合主键,实体代码如下:

@Data
@Entity
@Table(name = "customer")
@IdClass(CustomerPK.class)
public class CustomerEO implements java.io.Serializable {

         private Integer id;     
         @Id
         private String name;
         @Id
         private String email;

@GeneratedValue(strategy=GenerationType.AUTO)

主键的生成策略。
IDENTITY:采用数据库 ID自增长的方式来自增主键字段,Oracle 不支持这种方式;
AUTO: JPA自动选择合适的策略,是默认选项;默认情况下,SqlServer 对应 identity,MySQL 对应 auto increment。
SEQUENCE:通过序列产生主键,通过 @SequenceGenerator 注解指定序列名,MySql 不支持这种方式
TABLE:通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植。

@Basic

@Basic 表示一个简单的属性到数据库表的字段的映射,对于没有任何标注的 getXxxx() 方法,默认即为
@Basic fetch: 表示该属性的读取策略,有 EAGER 和 LAZY 两种,分别表示主支抓取和延迟加载,默认为 EAGER.
optional:表示该属性是否允许为null, 默认为true

@Column

当实体的属性与其映射的数据库表的列不同名时需要使用@Column 标注说明,该属性通常置于实体的属性声明语句之前,还可与 @Id 标注一起使用。
name 设置映射数据库表的列名
unique 、nullable、length 等。

@Transient

表示该属性并非一个到数据库表的字段的映射,ORM框架将忽略该属性.

@Temporal

数据库中,表示 Date 类型的数据有 DATE, TIME, 和 TIMESTAMP 三种精度。在进行属性映射时可使用@Temporal注解来调整精度。

@Temporal(TemporalType.TIMESTAMP)// 时间戳
    public Date getCreatedTime() {
        return createdTime;
        }

@Temporal(TemporalType.DATE) //时间精确到天
    public Date getBirth() {
        return birth;
    }

对象关联关系

@OneToOne 并通过@JoinColumn指定了表之间的外键关系

@Entity
public class User{
	@OneToOne
	@JoinColumn(name="address_id")
	private Address address;
}

@ManyToOne 并通过@JoinColumn指定了表之间的外键关系
@OneToMany 并通过@JoinColumn指定了表之间的外键关系
@ManyToMany 并通过@JoinTable指定了表之间的外键关系

@Entity
public class Product{
	@ManyToMany 
	//以关联表product_catalog表示关系。
	@JoinTable(name="product_catalog",joinColumns = @JoinColumn(name = "product_id"), inverseJoinColumns = @JoinColumn(name="catalog_id"))
	private Set<Catalog> catalogs = new HashSet<Catalog>();
}

3)Repository接口及注解

JPQL(面向对象的SQL语法结构)

public interface UserinfoRepository extends JpaRepository<Userinfo, Long> {
    @Modifying
    @Query(value = "update userinfo set user_name = :name, user_tel = :tel where user_id = :id",nativeQuery = true)
    @Transactional
    void updateById(@Param("id") Long id, @Param("name") String name, @Param("tel") String tel);

    @Query(value = "select user_id from userinfo",nativeQuery = true)
    List<Long> getUserIdList();
}

以上类通常放在dao包下面。
外部使用时:

@Autowired
    ThreatRepository threatRepository;
    threatRepository.saveAll(list);

@Query(绑定自定义sql到自定义方法上)

当进行 find 操作时,JPA 在 EntityManager 中缓存了 find 生成的对象,当再次 find 时会直接返回该对象。
@Query 引起的数据库变更 EntityManager 并不能发现,更进一步说,使用其它工具或者其它框架修改数据库中的数据,也不能及时反应到 JPA 的 find 系列方法上来。

@Modifying(标注@Query绑定的sql涉及增删改)

加上这个注解,JPA会以更新类语句来执行,而不再是以查询语句执行。 
该注解中有两个属性:

  1. flushAutomatically:自动刷新,即执行完语句后立即将变化内容从缓存刷新到磁盘。
  2. clearAutomatically:自动清除缓存,即执行完语句后自动清除掉已经过期的实体,比如,我们删除了一个实体,但是在还没有执行flush操作时,这个实体还存在于实体管理器EntityManager中,但这个实体已经过期没有任何用处,直到flush操作时才会被删除掉。如果希望在删除该实体时立即将该实体从实体管理器中删除,则可以将该属性设置为true,会在更新完数据库后会主动清理一级缓存。
    自动清理之后还会带来一个新的问题,clear 操作清理的缓存中,还包括提交后未 flush 的数据,例如调用 save 而不是 saveAndFlush 就有可能不会立即将修改内容更新到数据库中,在 save 之后 flush 之前调用 @Modifying(clearAutomatically = true) 修饰的方法就有可能导致修改丢失。如果再要解决这个问题,还可以再加上另外一个属性 @Modifying(clearAutomatically = true, flushAutomatically = true),@Modifying 的 flushAutomatically 属性为 true 时,执行 modifying query 之前会先调用 flush 操作,从而避免数据丢失问题。
  3. 在实际运行中,clear 和 flush 操作都可能需要消耗一定的时间,要根据系统实际情况可以选择使用其中的一个或两个属性,以保证系统的正确性。
User user = userDao.getOneById(1);
userDao.updateName(user.getId, 'abc');
User user2 = userDao.getOneById(1);
System.out.println(user2.getName()); //输出的是旧数据,不是abc
String name = 'abc';
User user = userDao.getOneById(1);
user.setName(name);
userDao.updateName(user.getId, name);
User user2 = userDao.getOneById(1);
System.out.println(user2.getName()); //输出的是新数据,通过set修改了缓存中的内容

@Transactional

默认情况下,repository 接口中的CRUD方法都是被@Transactional注解修饰了的。

  • 如果在同一个类中,一个非@Transaction的方法调用有@Transaction的方法不会生效,因为代理问题。
  • 在类上相当于在每个public方法上加上@Transaction。
  1. 对于查sql,默认为:@Transactional(readOnly = true),即只读事务;
  2. 增删改sql,默认为:@Transactional(readOnly = false),即非只读事务;当一个事务是非只读事务的时候,我们可以进行任何操作。。
  3. 如果你需要修改repository 接口中的某些方法的事务属性,可以在该方法上重新加上@Transactional注解,并设置需要的属性。

4)Native SQL Query

1>查询

StringBuffer sql = new StringBuffer();
sql.append("select * from user where id = :id");
Map<String, Object> map = new HashMap<>();
map.put("id", id);

Query query = em.createNativeQuery(sql.toString(), User.class);
map.forEach(query::setParameter);
//UserVo必须添加@Entity注解(配置了Java类与数据库映射)。会在数据库自动建表,可以关闭jpa自动建表功能
List<UserVo> users = query.getResultList();

如上述方法,调用Query createNativeQuery(String var1, Class var2)方法时,po类如果是一个临时类,同样需要添加@Entity注释,在jpa开启自动建表的时候,会在数据库生成一些数据为空的表。
调用createNativeQuery(sql.toString()).unwrap(NativeQueryImpl.class)方法进行查询时,需要注意必需添加@Transactional(readOnly = true)才可以正常使用。

NativeQueryImpl query = em.createNativeQuery(sql.toString()).unwrap(NativeQueryImpl.class);
        query.setParameter(1, startDate);
        query.setParameter(2, endDate);
        query.setParameter(3, id);

        //自定义实体类
        query.setResultTransformer(Transformers.aliasToBean(User.class));
        //字段定义
        query.addScalar("date", StandardBasicTypes.STRING);
        query.addScalar("Grades", StandardBasicTypes.INTEGER);
        List<User> resultList = query.list();

一个查询理论上不用加事务注解,由于@Transactional是基于动态代理实现的,不加的话,他就找不到正确的转化对象。如果entityManager不是通过注解注入的,而是通过factroy工厂获得的话,就可以不加事务注解,正常查询。

2>更新

StringBuilder sb = new StringBuilder();
sb.append("update user set name = :name'")
            .append(……);
map.forEach(query::setParameter);
Query query = em.createNativeQuery(sb.toString());
query.executeUpdate();

3>分页查询

Query query = em.createNativeQuery(sql.toString(), User.class).setParameter(1, "aaa");
query.setFirstResult(page * size);
query.setMaxResults(size);
list = query.getResultList();

5)Pageable分页查询

import org.springframework.data.domain.Pageable;
@Query(value = "SELECT* FROM stu_tab ",nativeQuery = true)
    List<ThreatEntity> getThreatTrend_3h(Pageable pageable);
@RequestMapping(value = "/testPageable", method = RequestMethod.GET)
public Page<User> testPageable(
        @RequestParam("page") Integer page,  //页码
        @RequestParam("size") Integer size,    //每页数量 
        @RequestParam("sortType") String sortType,  //排序方式:ASC/DESC
        @RequestParam("sortableFields") String sortableFields  //排序字段
) {
    //判断排序类型及排序字段
    Sort sort = "ASC".equals(sortType) ? new Sort(Sort.Direction.ASC, sortableFields) : new Sort(Sort.Direction.DESC, sortableFields);
    //获取pageable
    Pageable pageable = new PageRequest(page-1,size,sort);
    return userRepository.findAll(pageable);
}

踩坑:sql语句中有distinct但无order by。加入pageable后报错如下:

SELECT DISTINCT ON expressions must match initial ORDER BY expressions

原因是:
1,在sql中当order by和distinct同时使用时,如果指定了 SELECT DISTINCT,那么 ORDER BY 子句中的项就必须出现在选择列表中。
2,在sql中加入order by语句即可解决该问题。
3,pageable 中 的sort会自动在sql后面拼接order by语句。

6)批量插入删除

jpa的批量插入效率较低,在要求高性能批量插入的情况下可以使用spring jdbc进行批量插入,可以明显提升插入效率。
同理,jpa中自带的deleteBy…方法性能很差,遍历所有,根据id删除,所以需要自己写本地sql进行删除。

jpa批量插入慢原因

jpa插入源码:

@Transactional
    public <S extends T> List<S> saveAll(Iterable<S> entities) {

        Assert.notNull(entities, "The given Iterable of entities not be null!");

        List<S> result = new ArrayList<S>();

        for (S entity : entities) {
            result.add(save(entity));
        }

        return result;
    }
@Transactional
    public <S extends T> S save(S entity) {

        if (entityInformation.isNew(entity)) {
            em.persist(entity);
            return entity;
        } else {
            return em.merge(entity);
        }
    }

查看源码可以知道批量插入saveAll 循环对每个对象进行save操作,每次save时会对对象做存在性检查,就是先查一遍,要是不存在才会保存。

7)inet(IPv4 或者 IPv6 网络地址),jsonb

1>UserType和方言

JPA不能识别pg的一些类型:inet,jsonb。为了自动转换需要开发者自定义一些类型,称为方言。

  1. 为了让JPA支持jsonb,需要自定义PostgreSQL9Dialect
import org.hibernate.dialect.PostgreSQL9Dialect;

import java.sql.Types;

public class CustomPostgreSqlDialect extends PostgreSQL9Dialect {

    public CustomPostgreSqlDialect() {
        super();
        this.registerColumnType(Types.JAVA_OBJECT, "jsonb");
    }
}
  1. 指定方言spring.jpa.database-platform: com.xxx.PostgreSqlDialect,如下
spring:
  datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://localhost:5432/postgres
    username: postgres
    password: 123456
    hikari:
      connection-timeout: 20000
      maximum-pool-size: 5
  jpa:
    hibernate:
      ddl-auto: create
    show-sql: true
    database-platform: com.example.jsonb.config.CustomPostgreSqlDialect
    properties:
      hibernate.temp.use_jdbc_metadata_defaults: false
logging:
  level:
    org.hibernate.type.descriptor.sql.BasicBinder: trace
  1. 自定义jsonb数据类型
public class JsonbMapType<T> implements UserType {}
public class JsonbListType<T> implements UserType {
public class InetType<T> implements UserType {}
  1. 然后就可以在PO中使用这种类型啦。

2>Po定义

@Entity
@TypeDefs({@TypeDef(name = "JsonbMapType", typeClass = JsonbMapType.class),
        @TypeDef(name = "JsonbListType", typeClass = JsonbListType.class),
        @TypeDef(name = "InetType", typeClass = InetType.class)})
@Table(name = "test_table")
@Data
public class TestObjcetPo implements Serializable {

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

    @Type(type = "InetType")
    @Column(name = "ip", columnDefinition = "inet")
    private String ip;

    @Column(name = "test_objcet1s")
    @Type(type = "JsonbListType")
    private List<TestObject1> testObject1s;

    @Temporal(TemporalType.TIMESTAMP)
    @CreationTimestamp  #加了这个注解会自动更新创建时间
    @Column(name = "create_time")
    private Date createTime;

    @Temporal(TemporalType.TIMESTAMP)
    @UpdateTimestamp #加了这个注解会自动更新更新时间
    @Column(name = "update_time")
    private Date updateTime;

}

3>sql查询:host(ip)

@Query(value = "SELECT *  FROM test_table WHERE id= :id and host(ip) in (:ipList) ", nativeQuery = true)
List<TestObject> getObjectByIdAndIpList(@Param("id") long id, @Param("ipList") List<String> ipList);

4>批量操作:jdbcTemplate.batchUpdate

@Transactional(rollbackFor = Exception.class)
public int[] saveBatch(List<TestObject> list, Date nowTime) {

    return jdbcTemplate.batchUpdate("insert into test_tabkle ( update_time, soft_infos, ip) " +
                    "values ( ?, to_json(?::json), ?::inet",
            new BatchPreparedStatementSetter() {
                @Override
                public void setValues(PreparedStatement ps, int i) throws SQLException {
                    ps.setObject(1, new Timestamp(nowTime.getTime()));
                    ps.setObject(2, JSON.toJSONString(list.get(i).getSoftInfos()));
                    ps.setObject(3, potentialRiskList.get(i).getIp());
                }

                @Override
                public int getBatchSize() {
                    return potentialRiskList.size();
                }
            });
}

8)jpa的更新操作-传入对象

字段有值就更新,没值就用原来的:

/**
     *复杂JPA操作  使用@Query()自定义sql语句  根据业务id UId去更新整个实体
     * 删除和更新操作,需要@Modifying和@Transactional注解的支持
     *
     * 更新操作中 如果某个字段为null则不更新,否则更新【注意符号和空格位置】
     *
     * @param huaYangArea   传入实体,分别取实体字段进行set
     * @return  更新操作返回sql作用条数
     */
    @Modifying
    @Transactional
    @Query("update HuaYangArea hy set " +
            "hy.areaName = CASE WHEN :#{#huaYangArea.areaName} IS NULL THEN hy.areaName ELSE :#{#huaYangArea.areaName} END ," +
            "hy.areaPerson = CASE WHEN :#{#huaYangArea.areaPerson} IS NULL THEN hy.areaPerson ELSE :#{#huaYangArea.areaPerson} END ," +
            "hy.updateDate = CASE WHEN :#{#huaYangArea.updateDate} IS NULL THEN hy.updateDate ELSE :#{#huaYangArea.updateDate} END ," +
            "hy.updateId =  CASE WHEN :#{#huaYangArea.updateId} IS NULL THEN hy.updateId ELSE :#{#huaYangArea.updateId} END " +
            "where hy.uid = :#{#huaYangArea.uid}")
    int update(@Param("huaYangArea") HuaYangArea huaYangArea);