SpringDataJPA快速使用.doc
Spring Data JPA快速启动目录Spring Data JPA快速启动11概述12 Spring Data JPA使用12.1实体类规:22.2 Dao接口规:22.3 自定义查询42.4 配置文件规62.5事务处理92.6 为接口中的局部方法提供自定义实现92.7 锁102.8 应用场景以及优点112.9 JPA的缺陷:111概述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> 部使用 <conte*t:include-filter>、<conte*t:e*clude-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接口需要继承BaseDaoe.g:public interface PersonDao e*tends BaseDao<Person, Long>Person:Dao对应实体类类型Long:Dao对应实体类主键ID类型import java.io.Serializable;import java.util.List;import org.springframework.data.domain.Page;importin.Pageable;import org.springframework.data.domain.Sort;import org.springframework.data.repository.Repository;publicinterface BaseDao<T, ID e*tends Serializable> e*tends Repository<T, ID> BaseDao中提供了假设干默认方法,可以直接进展调用,描述如下:1、 public <S e*tends T> List<S> save(Iterable<S> entities)/批量新增或编辑2、 public T save(T entity); / 保存或更新2、public T delete(T entity); / 根据ID删除3、public T findOne(ID id); / 根据ID查询4、public boolean e*ists(ID id); / 根据ID查找是否存在5、public List<T> findAll(); / 查询所有6、public List<T> findAll(Iterable<ID> ids); / 根据ID集合查找7、public long count(); / 查询记录总数8、public Page<T> findAll(Pageable pageable); / 分页查询参数pageable:可以传递new PageRequest(0, 10)0表示第一页10表示每页10条数据返回值Page对象使用:当前页:getNumber();每页显示条目数:getSize();总页数:getTotalPages();结果集总数量:getTotalElements();是否是首页:isFirstPage();是否是末页:isLastPage();是否有上一页:hasPreviousPage();是否是下一页:hasNe*tPage();查询结果集:getContent();9、public List<T> findAll(Sort sort); / 排序查询参数sort可以传递:new Sort(new Order(Direction.DESC, "personId"), new Order(Direction.ASC, "age")Direction.DESC:降序排列Direction.ASC:生序排列personId和age:列名其他方法可以进展自定义,方式参考代码例2.3 自定义查询支持两种模式的自定义查询:(1) 使用 Query 创立查询Query 注解的使用非常简单,只需在声明的方法上面标注该注解,同时提供一个 JP QL 查询语句即可,如下所示:Query 支持命名参数例如 public interface UserDao e*tends 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);框架在进展方法名解析时,会先把方法名多余的前缀截取掉,比方 find、findBy、read、readBy、get、getBy,然后对剩下局部进展解析。并且如果方法的最后一个参数是 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 ma*, int min);· LessThan - 等价于 SQL 中的 "<",比方 findBySalaryLessThan(int ma*);· 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.*ml路径:classpath?META-INF?persistence.*ml容:<?*mlversion="1.0"encoding="UTF-8"?><persistence*mlns="java.sun./*ml/ns/persistence"version="2.0"><!- Name属性用于定义持久化单元的名字 (name必选,空值也合法); transaction-type(RESOURCE_LOCAL : 本地事物,针对一个数据库;JTA:全局事物,跨数据库) -><persistence-unitname="student"transaction-type="RESOURCE_LOCAL"><class></class><class></class><class></class><class></class><class></class><!- 用于指定持久化实现厂商类 -><provider></provider><!- 配置数据库类型 mysql:"java:/MySqlDS" oradle:"java:/OracleDS" -><jta-data-source>java:/MySqlDS</jta-data-source><!- 可选 -><properties><propertyname="hibernate.dialect"value="org.hibernate.dialect.MySQLDialect"/><propertyname="packagesToScan"value="cn.damai.student.core.domain"/><!- 配置是否自动创立数据库表 create:加载时,根据实体类重新创立表构造 create-drop:加载时,根据实体类重新创立表构造,退出时,删除表构造 update:运行时根据实体类生成表构造,如果实体类和表构造不同,会更新表构造 validate:加载时验证实体类和表构造是否一样,不同报异常 -><propertyname="hibernate.hbm2ddl.auto"value="update"/></properties></persistence-unit></persistence>applicationConte*t.*ml<?*mlversion="1.0"encoding="UTF-8"?><beans*mlns=".springframework.org/schema/beans"*mlns:*si=".w3.org/2001/*MLSchema-instance"*mlns:conte*t=".springframework.org/schema/conte*t"*mlns:t*=".springframework.org/schema/t*"*mlns:jpa=".springframework.org/schema/data/jpa"*si:schemaLocation=".springframework.org/schema/beans .springframework.org/schema/beans/spring-beans.*sd .springframework.org/schema/conte*t .springframework.org/schema/conte*t/spring-conte*t.*sd .springframework.org/schema/t* .springframework.org/schema/t*/spring-t*-2.5.*sd .springframework.org/schema/data/jpa .springframework.org/schema/data/jpa/spring-jpa-1.0.*sd"><!- spring 组件扫描 -><conte*t:component-scanbase-package="cn.damai"/><!- 数据源 -><beanid="student_dataSource"class=".mchange.v2.c3p0.ComboPooledDataSource"destroy-method="close"><propertyname="driverClass"value=".mysql.jdbc.Driver"/><propertyname="jdbcUrl"value="jdbc:mysql:/192.168.66.10:3306/new_schema"/><propertyname="user"value="root"/><propertyname="password"value="mysql"/><propertyname="initialPoolSize"value="2"/><propertyname="minPoolSize"value="2"/><propertyname="ma*PoolSize"value="50"/><propertyname="ma*IdleTime"value="25000"/><propertyname="acquireIncrement"value="2"/><propertyname="idleConnectionTestPeriod"value="60"/><propertyname="testConnectionOnCheckout"value="true"/><propertyname="testConnectionOnCheckin"value="true"/><propertyname="automaticTestTable"value="test_c3p0"/><propertyname="checkoutTimeout"value="5000"/></bean><!- JPA实体管理器工厂 -><beanid="student_entityManagerFactory"class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"><propertyname="dataSource"ref="student_dataSource"/><propertyname="persistenceUnitName"value="student"/><propertyname="persistence*mlLocation"value="classpath*:META-INF/persistence.*ml"/><propertyname="jpaVendorAdapter"><beanclass="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"><propertyname="generateDdl"value="false"/><propertyname="showSql"value="true"/></bean></property></bean><!- 使用annotation定义事务 -><t*:annotation-driventransaction-manager="transactionManager"/><beanid="transactionManager"class="org.springframework.orm.jpa.JpaTransactionManager"><propertyname="entityManagerFactory"ref="student_entityManagerFactory"/></bean><!- 在效劳启动时,将dao层接参加到容器管理中。CustomImpl为扩展实现类标识如果有实现类的话 transaction-manager-ref="transactionManager"-><jpa:repositoriesbase-package="cn.damai.student.core.dao,cn.damai.student.impl.custom.dao"query-lookup-strategy="create-if-not-found"repository-impl-postfi*="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注解的noRollbackFor和rollbackFor属性如:Transactional(rollbackFor=E*ception.class)可以使checked异常发生时,数据库操作也rollback、Transactional(noRollbackFor=RuntimeE*ception.class)可以使unchecked异常发生时也提交数据库操作。也可以使用noRollbackForClassName、rollbackForClassName属性来指定一个异常类名的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 RuntimeE*ception("注释");)会回滚,即遇到不受检查unchecked的例外时回滚;而遇到需要捕获的例外(throw new E*ception("注释");)不会回滚,即遇到受检查的例外就是非运行时抛出的异常,编译器会检查到的异常叫受检查例外或说受检查异常时,需我们指定方式来让事务回滚 ,如下:Transactional(rollbackFor=E*ception.class) /指定回滚,遇到异常E*ception时回滚 public void methodName() throw new E*ception("注释"); Transactional(noRollbackFor=E*ception.class)/指定不回滚,遇到运行期例外(throw new RuntimeE*ception("注释");)会回滚 public ItimDaoImpl getItemDaoImpl() throw new RuntimeE*ception("注释"); 2.6 为接口中的局部方法提供自定义实现有些时候,可能需要在*些方法中做一些特殊的处理,此时自动生成的代理对象不能完全满足要求。为了能够为局部方法提供自定义实现,我们可以采用如下的方法:· 将需要手动实现的方法从持久层接口假设为 PersonDao 中抽取出来,独立成一个新的接口假设为PersonDaoCustom ;· 为 PersonDaoCustom 提供自定义实现假设为 PersonDaoCustomImpl ;· 将 PersonDaoCustomImpl 配置为 Spring Bean;· 在 <jpa:repositories> 中指定实现类的后缀比方“CustomImpl。<jpa:repositories > 提供了一个 repository-impl-postfi* 属性,用以指定实现类的后缀。假设做了如下配置:设置自动查找时默认的自定义实现类命名规则 <jpa:repositories base-package="" repository-impl-postfi*="CustomImpl"/> 则在框架扫描到 PersonDao 接口时,它将尝试在一样的包目录下查找 PersonDaoCustomImpl.java,如果找到,便将其中的实现方法作为最终生成的代理类中相应方法的实现。一般情况下我们如下进展配置,把实现类的包纳入jpa扫描路径:<!- 在效劳启动时,将dao层接参加到容器管理中。CustomImpl为扩展实现类标识如果有实现类的话 -><!- 在效劳启动时,将dao层接参加到容器管理中。CustomImpl为扩展实现类标识如果有实现类的话 -><jpa:repositoriesbase-package="query-lookup-strategy="create-if-not-found"repository-impl-postfi*="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实体管理器在事务提交前都会锁