Spring Data JPA快速启动

来源:互联网 发布:战舰世界 岛风配件数据 编辑:程序博客网 时间:2024/05/18 07:12

Spring Data JPA快速启动


 

1概述

Spring Data JPA Spring Data家族的提供的一个持久层框架,可以自动创建dao实现类和自定义查询,简化了持久层代码。

 

2 Spring Data JPA使用

使用Spring Data JPA只需要3个步骤:

(1)声明持久层的接口,该接口继承 Repository,Repository 是一个标记型接口,它不包含任何方法,当然如果有需要,Spring Data 也提供了若干 Repository 子接口,其中定义了一些常用的增删改查,以及分页相关的方法。

(2)在接口中声明需要的业务方法。Spring Data 将根据给定的策略来为其生成实现代码。

(3)在 Spring 配置文件中增加一行声明,让 Spring 为声明的接口创建代理对象。配置了 <jpa:repositories> 后,Spring 初始化容器时将会扫描 base-package 指定的包目录及其子目录,为继承 Repository 或其子接口的接口创建代理对象,并将代理对象注册为 Spring Bean,业务层便可以通过 Spring 自动封装的特性来直接使用该对象。

此外,<jpa:repository> 还提供了一些属性和子标签,便于做更细粒度的控制。可以在<jpa:repository> 内部使用<context:include-filter><context:exclude-filter>来过滤掉一些不希望被扫描到的接口。

2.1实体类规范:

实体类均使用注解进行配置,常用注解描述如下,其他请参考JPA配置规范:

@Entity:bean的最上面使用,使实体bean映射到数据库中.

@Table(name="tableName"):指定实体bean映射到数据库中的表名.

@Id @GeneratedValue(strategy=GenerationType.AUTO)

Id:指定实体bean在数据库表的实体标识属性 

Strategy:id的生成策略.

GenerationType.AUTO:自动(默认值,可以不设定)

GenerationType.IDENTITY:数据库的id自增长(Oracle不支持)

GenerationType. SEQUENCE:序列化(Mysql不支持)

GenerationType.TABLE:表生成  

@Column(length=20,nullable=false,name="") :字段属性注释 

Length:指定字段的长度 

Nullable:是否为空.false:不为空;true:空 

Name:指定字段映射到表的列名

@Temporal(TemporalType.DATE):指定类型为时间的字段属性的映射 

DATE:日期,不包括时间.1985-09-09

TIME:时间,没有日期.23:44:00

TIMESTAMP:时间戳,包括日期和时间1985-09-09 23:44:00  

@Enumerated(EnumType.STRING):枚举类型属性的映射 

EnumType.STRING:保存枚举类型数据的字符类型数据到数据库 

EnumType.ORDINAL:保存枚举类型数据的索引值到数据库 

@Lob:大文本字段(String)和二进制数组类型字段(Byte[])使用它,对应数据库的大文本类型.

@Transient : 属性不跟数据库表进行映射使用Transient注解  

@Basic(fetch=FetchType.LAZY): 延时加载数据使用Basic注解中的

FatchType:LAZY 懒加载;EAGER立即

 

2.2 Dao接口规范:

Dao接口需要继承BaseDao

e.gpublic interface PersonDao extends BaseDao<Person, Long>

Person:Dao对应实体类类型

LongDao对应实体类主键ID类型

 

import java.io.Serializable;

import java.util.List;

 

import org.springframework.data.domain.Page;

import org.springframework.data.domain.Pageable;

import org.springframework.data.domain.Sort;

import org.springframework.data.repository.Repository;

 

public interface BaseDao<T, IDextends Serializable> extends Repository<T, ID> {

 

}

BaseDao中提供了若干默认方法,可以直接进行调用,描述如下:

1、 public <S extends T> List<S> save(Iterable<S> entities)//批量新增或编辑

2、 public T save(T entity); // 保存或更新

2public T delete(T entity); //根据ID删除

3public T findOne(ID id); //根据ID查询

4public boolean exists(ID id); //根据ID查找是否存在

5public List<T> findAll(); //查询所有

6public List<T> findAll(Iterable<ID> ids); //根据ID集合查找

7public long count(); //查询记录总数

8public Page<T> findAll(Pageable pageable); //分页查询

参数pageable:可以传递new PageRequest(0, 10)

0表示第一页

10表示每页10条数据

返回值Page对象使用:

当前页:getNumber();

每页显示条目数:getSize();

总页数:getTotalPages();

结果集总数量:getTotalElements();

是否是首页:isFirstPage();

是否是末页:isLastPage();

是否有上一页:hasPreviousPage();

是否是下一页:hasNextPage();

查询结果集:getContent();

9public List<T> findAll(Sort sort); //排序查询

参数sort可以传递:

new Sort(new Order(Direction.DESC, "personId"), new Order(Direction.ASC, "age"))

Direction.DESC:降序排列

Direction.ASC:生序排列

personIdage:列名

 

其他方法可以进行自定义,方式参考代码范例

2.3 自定义查询

支持两种模式的自定义查询:

(1) 使用 @Query 创建查询

@Query 注解的使用非常简单,只需在声明的方法上面标注该注解,同时提供一个 JP QL查询语句即可,如下所示:

@Query 支持命名参数示例

 public interface UserDao extends BaseDao<AccountInfo, Long> {

 

 public AccountInfo save(AccountInfo accountInfo);

 //与参数的顺序无关

 @Query("from AccountInfo a where a.accountId = :id")

 public AccountInfo findByAccountId(@Param("id")Long accountId);

//与参数的顺序相关

 @Query("from AccountInfo a where a.accountId = ?1")

 public AccountInfo findById(Long accountId);

   

 

   

 }

此外,也可以通过使用 @Query 来执行一个更新操作,为此,我们需要在使用 @Query 的同时,用 @Modifying 来将该操作标识为修改查询,这样框架最终会生成一个更新的操作,而非查询。如下所示:

使用 @Modifying 将查询标识为修改查询

 @Modifying

 @Query("update AccountInfo a set a.salary = ?1 where a.salary < ?2")

 public int increaseSalary(int after, int before);

方法中的参数顺序与@Query中的变量顺序保持一致。

 

(2)通过解析方法名创建查询

例1. 
根据命名规则写对应的抽象方法即可.根据 username 查找 user.只需在 UserDao 中 填写如下方法.

Java代码

1. public List<User> findByUsername(String username);  

例2. 
根据两个属性查询.

Java代码

1. public List<User> findByUsernameAndPassword(String username,String password);

框架在进行方法名解析时,会先把方法名多余的前缀截取掉,比如 findfindByreadreadBygetgetBy,然后对剩下部分进行解析。并且如果方法的最后一个参数是Sort 或者Pageable 类型,也会提取相关的信息,以便按规则进行排序或者分页查询。

 

在创建查询时,我们通过在方法名中使用属性名称来表达,比如 findByUserAddressZip ()。框架在解析该方法时,首先剔除findBy,然后对剩下的属性进行解析,详细规则如下(此处假设该方法针对的域对象为AccountInfo 类型):

· 先判断 userAddressZip (根据POJO 规范,首字母变为小写,下同)是否为AccountInfo 的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,继续第二步;

· 从 右往左截取第一个大写字母开头的字符串(此处为 Zip),然后检查剩下的字符串是否为AccountInfo 的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,则重复第二步,继续从右往左截取;最后假设user AccountInfo 的一个属性;

· 接 着处理剩下部分( AddressZip ),先判断user 所对应的类型是否有addressZip 属性,如果有,则表示该方法最终是根据"AccountInfo.user.addressZip" 的取值进行查询;否则继续按照步骤2 的规则从右往左截取,最终表示根据"AccountInfo.user.address.zip" 的值进行查询。

可 能会存在一种特殊情况,比如 AccountInfo 包含一个user 的属性,也有一个userAddress 属性,此时会存在混淆。读者可以明确在属性之间加上"_" 以显式表达意图,比如"findByUser_AddressZip()" 或者"findByUserAddress_Zip()"

 

在查询时,通常需要同时根据多个属性进行查询,且查询的条件也格式各样(大于某个值、在某个范围等等),Spring Data JPA为此提供了一些表达条件查询的关键字,大致如下:

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

· NotIn --- 等价于 SQL中的 "not in",比如findByUsernameNotIn(Collection<String> userList),方法的参数可以是 Collection类型,也可以是数组或者不定长参数;

 

创建查询的顺序

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

· create --- 通过解析方法名字来创建查询。即使有符合的命名查询,或者方法通过@Query 指定的查询语句,都将会被忽略。

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

· use-declared-query --- 如果方法通过 @Query指定了查询语句,则使用该语句实现查询;如果没有,则查找是否定义了符合条件的命名查询,如果找到,则使用该命名查询;如果两者都没有找到,则抛出异常

 

2.4 配置文件规范

persistence.xml

路径:classpath——》META-INF——》persistence.xml

内容:

<?xml version="1.0" encoding="UTF-8"?>

<persistence xmlns="http://java.sun.com/xml/ns/persistence"

version="2.0">

<!-- Name属性用于定义持久化单元的名字(name必选,空值也合法); transaction-type(RESOURCE_LOCAL

: 本地事物,针对一个数据库;JTA:全局事物,跨数据库) -->

<persistence-unit name="student" transaction-type="RESOURCE_LOCAL">

<class>cn.damai.student.core.domain.Course</class>

<class>cn.damai.student.core.domain.StudentCourse</class>

<class>cn.damai.student.core.domain.Student</class>

<class>cn.damai.student.core.domain.Teacher</class>

<class>cn.damai.student.core.domain.Department</class>

<!-- 用于指定持久化实现厂商类-->

<provider>org.hibernate.ejb.HibernatePersistence</provider>

<!-- 配置数据库类型 mysql:"java:/MySqlDS";oradle:"java:/OracleDS" -->

 <jta-data-source>java:/MySqlDS</jta-data-source> 

<!-- 可选 -->

<properties>

<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />

<property name="packagesToScan" value="cn.damai.student.core.domain" />

<!-- 配置是否自动创建数据库表create:加载时,根据实体类重新创建表结构create-drop:加载时,根据实体类重新创建表结构,退出时,删除表结构

update:运行时根据实体类生成表结构,如果实体类和表结构不同,会更新表结构validate:加载时验证实体类和表结构是否相同,不同报异常-->

<property name="hibernate.hbm2ddl.auto" value="update" />

</properties>

</persistence-unit>

</persistence>

applicationContext.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: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/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.0.xsd">

<!-- spring 组件扫描-->

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

<!-- 数据源 -->

<bean id="student_dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"

destroy-method="close">

<property name="driverClass" value="com.mysql.jdbc.Driver" />

<property name="jdbcUrl" value="jdbc:mysql://192.168.66.10:3306/new_schema" />

<property name="user" value="root" />

<property name="password" value="mysql" />

<property name="initialPoolSize" value="2" />

<property name="minPoolSize" value="2" />

<property name="maxPoolSize" value="50" />

<property name="maxIdleTime" value="25000" />

<property name="acquireIncrement" value="2" />

<property name="idleConnectionTestPeriod" value="60" />

<property name="testConnectionOnCheckout" value="true" />

<property name="testConnectionOnCheckin" value="true" />

<property name="automaticTestTable" value="test_c3p0" />

<property name="checkoutTimeout" value="5000" />

</bean>

 

<!-- JPA实体管理器工厂-->

<bean id="student_entityManagerFactory"

class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">

<property name="dataSource" ref="student_dataSource" />

<property name="persistenceUnitName" value="student" />

<property name="persistenceXmlLocation" value="classpath*:META-INF/persistence.xml" />

<property name="jpaVendorAdapter">

<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">

<property name="generateDdl" value="false" />

<property name="showSql" value="true" />

</bean>

</property>

</bean>

 

<!-- 使用annotation定义事务-->

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

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">

<property name="entityManagerFactory" ref="student_entityManagerFactory" />

</bean>

<!-- 在服务启动时,将dao层接加入到容器管理中。CustomImpl为扩展实现类标识(如果有实现类的话)transaction-manager-ref="transactionManager"-->

<jpa:repositories

base-package="cn.damai.student.core.dao,cn.damai.student.impl.custom.dao"

query-lookup-strategy="create-if-not-found" repository-impl-postfix="CustomImpl"

entity-manager-factory-ref="student_entityManagerFactory">

</jpa:repositories>

</beans>

 

2.5事务处理

可以在业务层(Service)使用spring事务。

默认情况下,Spring Data JPA 实现的方法都是使用事务的。针对查询类型的方法,其等价于 @Transactional(readOnly=true);增删改类型的方法,等价于 @Transactional。可以看出,除了将查询的方法设为只读事务外,其他事务属性均采用默认值。

如果用户觉得有必要,可以在接口方法上使用 @Transactional 显式指定事务属性,该值覆盖 Spring Data JPA 提供的默认值。同时,也可以在业务层方法上使用 @Transactional 指定事务属性,这主要针对一个业务层方法多次调用持久层方法的情况。持久层的事务会根据设置的事务传播行为来决定是挂起业务层事务还是加入业务层的事务。

 

@Transactional只能被应用到public方法上, 对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能.

Spring使用声明式事务处理,默认情况下,如果被注解的数据库操作方法中发生了unchecked异常,所有的数据库操作将rollback;如果发生的异常是checked异常,默认情况下数据库操作还是会提交的。
这种默认的行为是可以改变的。

使用@Transactional注解的noRollbackForrollbackFor属性

如:@Transactional(rollbackFor=Exception.class)可以使checked异常发生时,数据库操作也rollback、@Transactional(noRollbackFor=RuntimeException.class)可以使unchecked异常发生时也提交数据库操作。

也可以使用noRollbackForClassNamerollbackForClassName属性来指定一个异常类名的String数组来改变默认的行为。

读取数据库中的数据时是不需要事务管理的,这种情况下可以使用事务的传播行为来告诉Spring不需要开启事务,
如:@Transactional(propagation = Propagation.NOT_SUPPORTED)。

事务的传播行为有:

1.REQUIRED:表示业务方法需要在一个事务中处理,如果业务方法执行时已经在一个事务中,则加入该事务,否则重新开启一个事务。这也是默认的事务传播行为;

2. NOT_SUPPORTED:声明业务方法不需要事务,如果业务方法执行时已经在一个事务中,则事务被挂起,等方法执行完毕后,事务恢复进行;

3. REQUIRES_NEW:表明业务方法需要在一个单独的事务中进行,如果业务方法进行时已经在一个事务中,则这个事务被挂起,并重新开启一个事务来执行这个业务方法,业务方法执行完毕后,原来的事务恢复进行;

4. MANDATORY:该属性指定业务方法只能在一个已经存在的事务中进行,业务方法不能发起自己的事务;如果业务方法没有在一个既有的事务中进行,容器将抛出异常;

5. SUPPORTS:该属性指定,如果业务方法在一个既有的事务中进行,则加入该事务;否则,业务方法将在一个没有事务的环境下进行;

6. NEVER:指定业务方法不可以在事务中进行,如果业务方法执行时已经在一个事务中,容器将抛出异常;

7. NESTED:该属性指定,如果业务方法在一个既有的事务中执行,则该业务方法将在一个嵌套的事务中进行;否则,按照REQUEIRED来对待。它使用一 个单独的事务,这个事务可以有多个rollback点,内部事务的rollback对外部事务没有影响,但外部事务的rollback会导致内部事务的 rollback。这个行为只对DataSourceTransactionManager有效。

 

//事务传播属性
    @Transactional(propagation=Propagation.REQUIRED) //如果有事务,那么加入事务,没有的话新建一个(不写的情况下)
    @Transactional(propagation=Propagation.NOT_SUPPORTED) //容器不为这个方法开启事务
    @Transactional(propagation=Propagation.REQUIRES_NEW) //不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
    @Transactional(propagation=Propagation.MANDATORY) //必须在一个已有的事务中执行,否则抛出异常
    @Transactional(propagation=Propagation.NEVER) //必须在一个没有的事务中执行,否则抛出异常(Propagation.MANDATORY相反)
    @Transactional(propagation=Propagation.SUPPORTS) //如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.
    
 
    @Transactional(propagation=Propagation.NESTED)
    @Transactional (propagation = Propagation.REQUIRED,readOnly=true) //readOnly=true只读,不能更新,删除
    @Transactional (propagation = Propagation.REQUIRED,timeout=30)//设置超时时间
    @Transactional (propagation = Propagation.REQUIRED,isolation=Isolation.DEFAULT)//设置数据库隔离级别


spring 事务管理器,spring来负责数据库的打开,提交,回滚.
默认遇到运行期例外(throw new RuntimeException("注释");)会回滚,即遇到不受检查(unchecked)的例外时回滚;
而遇到需要捕获的例外(throw new Exception("注释");)不会回滚,即遇到受检查的例外(就是非运行时抛出的异常,编译器会检查到的异常叫受检查例外或说受检查异常)时,需我们指定方式来让事务回滚,如下:
@Transactional(rollbackFor=Exception.class) //指定回滚,遇到异常Exception时回滚
    public void methodName() {
       throw new Exception("注释");
    }
@Transactional(noRollbackFor=Exception.class)//指定不回滚,遇到运行期例外(throw new RuntimeException("注释");)会回滚
    public ItimDaoImpl getItemDaoImpl() {
        throw new RuntimeException("注释");
    }

 

 

2.6 为接口中的部分方法提供自定义实现

有些时候,可能需要在某些方法中做一些特殊的处理,此时自动生成的代理对象不能完全满足要求。为了能够为部分方法提供自定义实现,我们可以采用如下的方法:

· 将需要手动实现的方法从持久层接口(假设为 PersonDao )中抽取出来,独立成一个新的接口(假设为PersonDaoCustom );

· 为 PersonDaoCustom 提供自定义实现(假设为 PersonDaoCustomImpl );

· 将 PersonDaoCustomImpl 配置为 Spring Bean;

· 在 <jpa:repositories> 中指定实现类的后缀(比如“CustomImpl”)。

 

<jpa:repositories > 提供了一个 repository-impl-postfix 属性,用以指定实现类的后缀。假设做了如下配置:


设置自动查找时默认的自定义实现类命名规则

 <jpa:repositories base-package="cn.damai.jpa.demo.dao"

 repository-impl-postfix="CustomImpl"/>

 

则在框架扫描到 PersonDao 接口时,它将尝试在相同的包目录下查找 PersonDaoCustomImpl.java,如果找到,便将其中的实现方法作为最终生成的代理类中相应方法的实现。

一般情况下我们如下进行配置,把实现类的包纳入jpa扫描路径:

<!-- 在服务启动时,将dao层接加入到容器管理中。CustomImpl为扩展实现类标识(如果有实现类的话)-->

<!--在服务启动时,将dao层接加入到容器管理中。CustomImpl为扩展实现类标识(如果有实现类的话)-->

<jpa:repositories base-package="cn.damai.jpa.demo.dao,cn.damai.jpa.demo.custom.dao"

query-lookup-strategy="create-if-not-found" 

repository-impl-postfix="CustomImpl"

entity-manager-factory-ref="entityManagerFactory">

</jpa:repositories> 

 

2.7  

支持悲观锁 
锁是处理数据库事务并发的一种技术,当两个或更多数据库事务并发地访问相同数据时,锁可以保证同一时间只有一个事务可以修改数据。

锁的方法通常有两种:乐观锁和悲观锁。乐观锁认为多个并发事务之间很少出现冲突,也就是说不会经常出现同一时间读取或修改相同数据,在乐观锁中,其目标是 让并发事务自由地同时得到处理,而不是发现或预防冲突。两个事务在同一时刻可以访问相同的数据,但为了预防冲突,需要对数据执行一次检查,检`。

  悲观锁认为事务会经常发生冲突,在悲观锁中,读取数据的事务会锁定数据,在前面的事务提交之前,其它事务都不能修改数据。

JPA 1.0只支持乐观锁,你可以使用EntityManager类的lock()方法指定锁模式的值,可以是READ或WRITE,如:

引用


EntityManager em = ... ;
     em.lock (p1, READ);


对于READ锁模式,JPA实体管理器在事务提交前都会锁定实体,检查实体的版本属性确定实体自上次被读取以来是否有更新,如果版本属性被更新了,实体管理器会抛出一个OptimisticLockException异常,并回滚事务。

对于WRITE锁模式,实体管理器执行和READ锁模式相同的乐观锁操作,但它也会更新实体的版本列。

  JPA 2.0增加了6种新的锁模式,其中两个是乐观锁。JPA 2.0也允许悲观锁,并增加了3种悲观锁,第6种锁模式是无锁。

下面是新增的两个乐观锁模式: 

引用


   1、OPTIMISTIC:它和READ锁模式相同,JPA 2.0仍然支持READ锁模式,但明确指出在新应用程序中推荐使用OPTIMISTIC。
  2、OPTIMISTIC_FORCE_INCREMENT:它和WRITE锁模式相同,JPA 2.0仍然支持WRITE锁模式,但明确指出在新应用程序中推荐使用OPTIMISTIC_FORCE_INCREMENT。


下面是新增的三个悲观锁模式: 

引用


   1、PESSIMISTIC_READ:只要事务读实体,实体管理器就锁定实体,直到事务完成锁才会解开,当你想使用重复读语义查询数据时使用这种锁模式,换句话说就是,当你想确保数据在连续读期间不被修改,这种锁模式不会阻碍其它事务读取数据。
  2、PESSIMISTIC_WRITE:只要事务更新实体,实体管理器就会锁定实体,这种锁模式强制尝试修改实体数据的事务串行化,当多个并发更新事务出现更新失败几率较高时使用这种锁模式。
  3、PESSIMISTIC_FORCE_INCREMENT:当事务读实体时,实体管理器就锁定实体,当事务结束时会增加实体的版本属性,即使实体没有修改。


 你也可以指定新的锁模式NONE,在这种情况下表示没有锁发生。
  JPA 2.0也提供了多种方法为实体指定锁模式,你可以使用EntityManager的lock() 和 find()方法指定锁模式。此外,EntityManager.refresh()方法可以恢复实体实例的状态。
  下面的代码显示了使用PESSIMISTIC_WRITE锁模式的悲观锁:

引用


// read
     Part p = em.find(Part.class, pId);
      // lock and refresh before update
    em.refresh(p, PESSIMISTIC_WRITE);
     int pAmount = p.getAmount();
     p.setAmount(pAmount - uCount); 


在这个例子中,它首先读取一些数据,然后应用PESSIMISTIC_WRITE锁,在更新数据之前调用 EntityManager.refresh()方法,当事务更新实体时,PESSIMISTIC_WRITE锁锁定实体,其它事务就不能更新相同的实 体,直到前面的事务提交。

 

 

JPA默认应用层将使用乐观锁机制
使用乐观锁时entity必须声明一个version属性,该字段的值由持久化框架自动维护,应用层不能修改。持久化框架可以引入其它机制实现乐观锁检查,或者实现更细粒度的乐观锁控制,JPA不做要求
JPA的乐观锁机制是基于数据行的

JPA中声明了Lock Mode,可以实现悲观锁效果,至于实现方式不作具体要求,只要确保不存在P1: dirty readP2: non-repeatable read就行
LockModeTypeREADWRITE 2
对于WRITE模式,在isolation levelread commited情况下数据库可以确保不会出现P1P2两种状况
READ模式一般使用数据库的锁实现,用它实现悲观锁效果

 

可以使用 @Lock , 里面填入的是 JPA 的 LockModeType
*

@Lock(LockModeType.PESSIMISTIC_READ)

public List<User> findByUsername(String username);


对应的 sql 就是:

select * from t_user where username=? lock in share mode


*

@Lock(LockModeType.PESSIMISTIC_WRITE)

public List<User> findByUsername(String username);


对应的 sql 就是:

select * from t_user where username=? for update

 

使用@Lock注解即可实现锁。

2.8 应用场景以及优点 

JPA作为持久层框架有如下优点:

1.简单易用,帮助开发者提供了生产率

2.便于维护,减低了维护成本

3.学习成本相对比较低。

另外:

(1)标准化  

JPA  JCP 组织发布的 Java EE 标准之一,因此任何声称符合 JPA 标准的框架都遵循同样的架构,提供相同的访问 API,这保证了基于JPA开发的企业应用能够经过少量的修改就能够在不同的JPA框架下运行。
(2) 对容器级特性的支持 
  JPA 框架中支持大数据集、事务、并发等容器级事务,这使得 JPA 超越了简单持久化框架的局限,在企业应用发挥更大的作用。 
(3) 简单易用,集成方便 
   JPA的主要目标之一就是提供更加简单的编程模型:在JPA框架下创建实体和创建Java 类一样简单,没有任何的约束和限制,只需要使 用 javax.persistence.Entity进行注释;JPA的框架和接口也都非常简单,没有太多特别的规则和设计模式的要求,开发者可以很容易的掌握。JPA基于非侵入式原则设计,因此可以很容易的和其它框架或者容器集成。
(4) 可媲美JDBC的查询能力 
  JPA的查询 语言是面向对象而非面向数据库的,它以面向对象的自然语法构造查询语句,可以看成是Hibernate HQL的等价物。JPA定义了独特的JPQLJava Persistence Query Language),JPQLEJB QL的一种扩展,它是针对实体的一种查询语言,操作对象是实体,而不是关系数据库的表,而且能够支持批量更新和修改、JOINGROUP BYHAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。
(5) 支持面向对象的高级特性 
  JPA 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,这样的支持能够让开发者最大限度的使用面向对象的模型设计企业应用,而不需要自行处理这些特性在关系数据库的持久化。 
 

2.9 JPA的缺陷:

   JPA 的采用虽然大大简化了Entity Bean的使用.  但结果就有可能将数据库结构移植到你的应用程序的代码中。这将给应用程序的维护和升级带来麻烦。这里主要指通过Annotation对Entity Bean之间的关系进行定义。

这些数据实体的关系,如一对多,一对一,多对一,多对多及关联操作(cascade)等,很明显是数据库概念。为了实现ORM的目的,JPA不得不将其在Entity源代码中定义这种关系。

  这在很大程度上,将数据库的结构与设计移植到了应用程序中。这有以下几个问题:

  但是JPA的缺点也是显而易见,JPA作为持久层有如下缺点:

1.将语言与数据库混在一起,导致数据改动以后,配置文件必须更新

2.对于多数据与大数据量处理很容易产生性能问题。

3.过度封装,导致错误查找相对于JDBC等传统开发技术而言更加困难

 

3 0
原创粉丝点击