1、概述
1.1 定义
JPA Spring Data:致力于减少数据访问层(DAO)的开发量。开发者唯一要做的,就只是声明持久层的接口,其他都交给Spring DataJPA来帮你完成!
1.2 使用步骤
-
声明持久层的接口,该接口继承Repository
Repository是一个标记型接口,它不包含任何方法,如必要,Spring Data可实现Repository其他子接口,其中定义了一些常用的增删改查,以及分页相关的方法
-
在接口中声明需要的方法
Spring Data将根据给定的策略来为其生成实现代码
-
在Spring配置文件中配置Spring Data,让Spring为声明的接口创建代理对象
配置了
<jpa:repositories>
后,Spring初始化容器时将会扫描 base-package 指定的包目录及其子目录,为继承Repository或其子接口的接口创建代理对象,并将代理对象注册为Spring Bean,业务层便可以通过Spring自动封装的特性来直接使用该对象
2、环境搭建
- 配置文件
<?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/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 1. 配置自动扫描的包 -->
<context:component-scan base-package="com.moc"/>
<!-- 2. 配置数据源 -->
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
<!-- 配置其他属性 -->
</bean>
<!-- 3. 配置 JPA 的 EntityManagerFactory -->
<!-- 配置EntityManagerFactory相关属性设置 -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<!-- 配置数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- 配置扫描entity的路径 -->
<property name="packagesToScan" value="com.moc.entity"/>
<!-- 配置JPA的产品提供的适配器 -->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
</property>
<!-- 配置JPA的基本属性 -->
<property name="jpaProperties">
<props>
<!-- 二级缓存相关 -->
<!--
<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
<prop key="net.sf.ehcache.configurationResourceName">ehcache-hibernate.xml</prop>
-->
<!-- 生成的数据表的列的映射策略 -->
<prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop> <!--建表策略 -->
<!-- hibernate 基本属性 -->
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 4. 配置事务管理器 -->
<bean id="tm" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<!-- 5. 配置支持注解的事务 -->
<tx:annotation-driven transaction-manager="tm"/>
<!-- 6. 配置 SpringData -->
<!-- 加入 jpa 的命名空间 -->
<!-- base-package: 扫描 Repository Bean 所在的 package -->
<jpa:repositories base-package="com.moc.dao"
entity-manager-factory-ref="entityManagerFactory"
transaction-manager-ref="tm"/>
</beans>
3、Repository
3.1 Repository
是一个标记接口,继承图如下(PersonXxx为自定义的)
3.2 CrudRepository
-
方法
-
实例
@Test public void test2() { List<Person> list = new ArrayList<>(); list.add(new Person("zh1", "zh1@163.com", 22, new Date())); list.add(new Person("zh2", "zh3@163.com", 23, new Date())); list.add(new Person("zh3", "zh4@163.com", 24, new Date())); // Iterable -- 批量写入 personRepository.save(list); }
3.3 PagingAndSortingRepository
重要方法:Page<T> findAll(Pageable var1);
@Test
public void test3() {
Sort.Order order = new Sort.Order(Sort.Direction.ASC, "id");
List<Sort.Order> orders = new ArrayList<>();
orders.add(order);
Sort sort = new Sort(orders);
Pageable pageable = new PageRequest(0, 10, sort);
Page<Person> pages = personRepository.findAll(pageable);
System.out.println("pages.getTotalElements()" + pages.getTotalElements()); // 总条数
System.out.println("pages.getTotalPages()" + pages.getTotalPages()); // 总页数
System.out.println("pages.getSize()" + pages.getSize()); // 每页的条数
System.out.println("pages.getNumberOfElements()" + pages.getNumberOfElements()); // 得到的条数
System.out.println("pages.getContent()" + pages.getContent()); // 获取的内容
}
3.4 JpaRepository
- 方法
@Test
public void test4() {
Person person = new Person();
person.setAge(11);
person.setBirth(new Date());
person.setEmail("xxx@1ss.com");
person.setName("z33");
person.setId(1);
// 有主键更新,无主键插入
personRepository.saveAndFlush(person);
}
3.5 JpaSpecificationExecutor
不属于Repository体系,实现一组 JPA Criteria 查询相关的方法
- 方法
- 带条件得分页查询
public void test5() {
Sort.Order order = new Sort.Order(Sort.Direction.ASC, "id");
List<Sort.Order> orders = new ArrayList<>();
orders.add(order);
Sort sort = new Sort(orders);
Pageable pageable = new PageRequest(0, 10, sort);
Specification specification = new Specification<Person>(){
/**
* 带条件查询的分页
*
* @param root 导航root根对象,获得对象的属性(属性的属性的)path路径依赖
* @param criteriaQuery 几乎不用
* @param criteriaBuilder CriteriaBuilder的查询工厂类,类似where后面的各种条件组合
* @return Predicate Criteria的一个查询条件
*/
@Override
public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Predicate p = criteriaBuilder.gt(root.get("id"), 5); // gt 大于,ge 大于等于
Predicate p2 = criteriaBuilder.equal(root.get("name"), "gg");
return criteriaBuilder.and(p, p2);
}
};
Page<Person> pages = personRepository.findAll(specification, pageable);
System.out.println("pages.getTotalElements()" + pages.getTotalElements());
System.out.println("pages.getTotalPages()" + pages.getTotalPages());
System.out.println("pages.getSize()" + pages.getSize());
System.out.println("pages.getNumberOfElements()" + pages.getNumberOfElements());
System.out.println("pages.getContent()" + pages.getContent());
}
4、查询关键字
4.1 简单条件查询
-
简单条件查询:查询某一个实体类或者集合
-
按照 Spring Data 的规范,查询方法以 find | read | get 开头,
涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性以首字母大写
例如:定义一个 Entity 实体类
class User{ private String firstName; private String lastName; }
使用And条件连接时,应这样写:
findByLastNameAndFirstName(String lastName, String firstName);
条件的属性名称与个数要与参数的位置与个数一一对应
4.2 支持的关键字
直接在接口中定义查询方法,如果是符合规范的,可以不用写实现,目前支持的关键字写法如下:
4.3 查询方法解析流程
可以明确在属性之间加上 “_” 以显式表达意图,表示在分隔(在类里面)
@Table(name = "tbl_person")
@Entity
public class Person {
@Id
@GeneratedValue
private Integer id;
// 列名为address_id,同时外键关联
@JoinColumn(name = "address_id")
@ManyToOne
private Address address;
private String addressCity; // 区分上面
public Person(){}
}
@Table(name = "tbl_address")
@Entity
public class Address {
@Id
@GeneratedValue
private Integer id;
private String city;
public Address() { }
}
@Test
public void test7() {
// 查询类里的属性 AddressCity
List<Person> list1 = personRepository.getByAddressCityLike("%bj%");
// 查询类里的属性 Address 的级联的 City
List<Person> list2 = personRepository.getByAddress_CityLike("%bj%");
System.out.println(list1.size());
System.out.println(list2.size());
}
5、注解
5.1 @Query
这种查询可以声明在 Repository 方法中,摆脱像命名查询那样的约束,将查询直接在相应的接口方法中声明,结构更为清晰
- 索引参数如下所示,索引值从1开始,查询中 ”? X” 个数需要与方法定义的参数个数相一致,并且顺序也要一致
- 命名参数(推荐使用这种方式):可以定义好参数名,赋值时采用**@Param(“参数名”)**,而不用管顺序,SQL中使用 :参数名 。
- 如果是 @Query 中有 LIKE 关键字,后面的参数需要前面或者后面加 %,这样在传递参数值的时候就可以不加 %
// SQL中使用Entity类
@Query(value = "select p from Person p where p.name=? and p.email=?")
List<Person> getByNameAndEmail(String city, String name);
// 使用原生的SQL
@Query(value = "select email from tbl_person where name = :city and email = :name", nativeQuery = true)
List<String> getByNameAndEmail2(@Param("city") String city, @Param("name") String name);
// 测试
@Test
public void test8() {
List<Person> list = personRepository.getByNameAndEmail("rr", "rr@qq.com");
System.out.println(list.size());
List<String> list2 = personRepository.getByNameAndEmail2("rr", "rr@qq.com");
System.out.println(list.size());
}
// like
@Query("select o from UserModel o where o.name like ?1%")
public List<UserModel> findByUuidOrAge(String name);
@Query("select o from UserModel o where o.name like %?1")
public List<UserModel> findByUuidOrAge(String name);
@Query("select o from UserModel o where o.name like %?1%")
public List<UserModel> findByUuidOrAge(String name);
5.2 @Modifying
- @Query 与 @Modifying 这两个 annotation一起声明,可定义个性化更新操作,例如只涉及某些字段更新时最为常用
- 注意:
- 方法的返回值应该是 int,表示更新语句所影响的行数
- 在调用的地方必须加事务,没有事务不能正常执行
// dao层
@Modifying
@Query(value = "update Person p set p.email=? where p.id=?")
int updateEmail(String email, Integer id);
// service层
@Transactional
public int updateEmail(String email, Integer id) {
return personRepository.updateEmail(email, id);
}
// 测试
@Test
public void test9() {
// 需要事务
// int result = personRepository.updateEmail("abc@163.com", 1);
int result = personService.updateEmail("abc@163.com", 1);
System.out.println(result);
}
6、自定义Repository
为某一个 Repository 上添加自定义方法
实现步骤
-
新建xxxDao(PersonDao)
public interface PersonDao { void dealMethod(); }
-
新建xxxRepositorylmpl(PersonRepositoryImpl)类并实现PersonDao接口
关联entitymanager,标注@Repository
// Dao层
public interface PersonDao {
void dealMethod();
}
// 实现
@Repository
public class PersonRepositoryImpl implements PersonDao {
@PersistenceContext
private EntityManager entityManager;
@Override
public void dealMethod() {
System.out.println("*********" + entityManager.getClass().getName());
}
}
// 关联
public interface PersonRepository extends JpaRepository<Person, Integer>,
JpaSpecificationExecutor, PersonDao { }
// 测试
@Test
public void test10() {
personRepository.dealMethod();
}