Spring in action读书笔记(九) Spring与Java持久化API(JPA)
Java持久化API,即Java Persistence API (JPA),使用对象-关系映射持久化数据。
一: 配置EntityManager
EntityManage中是一个接口,定义了增、删、改、查的一些基本操作,都是由EntityManagerFactory创建,根据EntityManagerFactory创建和管理方式不同,可分为两类:
(1)应用程序管理类型:PersistenceProvider中方法createEntityManagerFactory创建EntityManagerFactory
(2)容器管理类型:PersistenceProvider中方法createContainerEntityManagerFactory创建EntityManagerFactory
Spring在查找entityManagerFactory实例时(?这个地方没有看懂),会查找到AbstractEntityManagerFactoryBean类,afterPropertiesSet方法中创建EntityManagerFactory实例及其代理,创建EntityManagerFactory的方法createNativeEntityManagerFactory为抽象方法,该方法在AbstractEntityManagerFactoryBean类的两个子类LocalContainerEntityManagerFactoryBean、LocalEntityManagerFactoryBean中实现,因此配置LocalContainerEntityManagerFactoryBean和LocalEntityManagerFactoryBean的bean实现,即可配置EntityManagerFactory。
1、配置应用程序管理类型的JPA(未实现)
2、配置容器管理类型的JPA
配置LocalContainerEntityManagerFactoryBean(本例中使用mysql数据库)
package test.config;
import org.apache.commons.dbcp2.BasicDataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.JpaVendorAdapter; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.vendor.Database; import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import javax.sql.DataSource; @Configurationpublic class JpaConfiguration { @Bean public JpaTransactionManager transactionManager() { return new JpaTransactionManager(); } @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) { LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean(); emf.setDataSource(dataSource); emf.setJpaVendorAdapter(jpaVendorAdapter); emf.setPackagesToScan("test"); return emf; } }
DataSource及JpaVendorAdapter的参数设置需要与使用的数据库对应。
其中DataSource为commons-dbcp2-2.5.0.jar包中的DataSource实现:BasicDataSource,配置用户名、密码、url。driver等
@Bean public DataSource dateSource() throws Exception { BasicDataSource dataSource = new BasicDataSource(); dataSource.setDriver(new com.mysql.cj.jdbc.Driver()); dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/test?userUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8"); dataSource.setUsername("root"); dataSource.setPassword("admin"); dataSource.setInitialSize(5); dataSource.setMaxTotal(10); return dataSource; }
JpaVendorAdapter有多种实现,这里选择HibernateJpaVendorAdapter
@Bean public HibernateJpaVendorAdapter jpaVendorAdapter() { HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter(); adapter.setDatabase(Database.MYSQL); adapter.setShowSql(true); adapter.setGenerateDdl(true); adapter.setDatabasePlatform("org.hibernate.dialect.MySQLDialect"); return adapter; }
二: 编写基于JPA的Repository
可以注入EntityManagerFactory或者EntityManager实例,操作数据库。
假设数据库为category_,对应的类为Category,注解对应的字段
package test; import javax.persistence.*; @Entity @Table(name = "category_") public class Category { @Id @GeneratedValue(strategy= GenerationType.IDENTITY) private int id; @Column(name="name") private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Category{" + "id=" + id + ", name='" + name + '\'' + '}'; } }
junit测试如下:
package test; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import test.config.JpaConfiguration; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.PersistenceContext; import javax.persistence.PersistenceUnit; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {JpaConfiguration.class}) public class EntityManagerFactoryTest { @PersistenceUnit private EntityManagerFactory emf; @PersistenceContext private EntityManager em; @Test public void test() { Category category = emf.createEntityManager().find(Category.class, 52); System.out.println(category); category = em.find(Category.class, 52); System.out.println(category); } }
通过@PersistenceUnit注解注入EntityMangerFactory时,每次操作数据库都会创建一个EntityManager,而EntityManager并不是线程安全的,会造成线程安全性问题;
通过@PersistenceContext注解注入EntityManager时,实际上注入了一个EntityManager的代理,如果当前事务没有对应的EntityManager代理,则会创建一个新的。
三: 借助Spring Data实现自动化的JPA Repository
以接口的方式,创建Repository
package test; import org.springframework.data.jpa.repository.JpaRepository; public interface CategoryRepository extends JpaRepository<Category, Long> { }
注入CategoryRepository对象,可以使用Repository接口中定义的保存、删除、根据id查询方法。
配置Spring Data JPA:
在JPAconfiguration类上增加 @EnableJpaRepositories(basePackages="test")注解(basePackages指明扫描的包)
1、定义查询方法
查询方法定义规则如下:
查询动词 + 限制结果条件,其中查询动词可以为get、read、find以及count,get、read、find会返回对象,而count返回匹配对象的数量。
例如 readByName(String name) 会根据name属性查找。
readByNameIgnoringCase(String name) : 忽略名称大小写
具体查询方法定义见<<Spring in action4>> 11.3.1章节
2、声明自定义查询
如果方法命名约定很难表达预期,如查询 select c from category_ c where c.name like 'A%a' 语句对应的结果,
可以在CategoryRepository中使用@Query注解定义如下的查询方法(Category为对应的类名,不是数据库中表名)
@Query("select c from Category c where c.name like 'A%a'")
List<Category> findAllStartsWithA();
注入CategoryRepository方法后即可使用该方法。
3、混合自定义的功能
Spring Data JPA为接口生成实现的时候,会查找与接口名称相同并且添加了Impl后缀的一个类。如果这个类存在,Spring Data JPA将会把它的方法与Spring Data JPA所生成的方法合并在一起。
如果方法命名约定或使用@Query都无法实现所需的方法,对CategoryRepository接口,可以在CategoryRepositoryImpl类中实现自定义的方法,将方法定义为接口
public interface CategoryUpdate { int updateCategory(); }
CategoryRepositoryImpl中实现该接口
package test; import org.springframework.data.jpa.repository.Modifying; import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; public class CategoryRepositoryImpl implements CategoryUpdate { @PersistenceContext private EntityManager em; public int updateCategory() { //这里写入需要执行的语句 String sql = "update Category c set c.name = 'Aa' where c.name = 'AA'"; return em.createQuery(sql).executeUpdate(); } }
修改CategoryRepository,使其继承自CategoryUpdate
package test; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import java.util.List; public interface CategoryRepository extends JpaRepository<Category, Long>, CategoryUpdate { Category findByName(String name); @Query("select c from Category c where c.name like 'A%'") List<Category> findAllStartsWithA(); }
junit进行测试:
@Autowired
private CategoryRepository categoryRepository;
@Test @Modifying @Transactional @Rollback(false) public void test3() { categoryRepository.updateCategory(); }
可以指定@EnableJpaRepositories中repositoryImplementationPostfix属性来设置CategoryRepository接口的实现类名称,其默认为Impl。