spring-data-jpa使用入门
spring-data-jpa的使用非常简单:
- 1.添加依赖
- 2.添加EntityManager相关配置
- 3.定义实体Bean,映射数据库表和字段
- 4.编写业务Repository
看个最简单的demo
1.pom.xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-bom</artifactId>
<version>2021.1.4</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.4.32.Final</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
</dependencies>
2.JpaConfiguration
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories("com.example.demo.dao")
public class JpaConfiguration {
@Bean
public DataSource dataSource() {
DruidDataSource ds = new DruidDataSource();
ds.setUrl("jdbc:mysql://localhost:3306/jpa_demo?allowMultiQueries=true&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true");
ds.setUsername("root");
ds.setPassword("123456");
return ds;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
vendorAdapter.setShowSql(true);
Properties props = new Properties();
props.put("hibernate.physical_naming_strategy", SpringPhysicalNamingStrategy.class.getName());
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.example.demo.entity");
factory.setDataSource(dataSource());
factory.setJpaProperties(props);
return factory;
}
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
}
3.实体Bean:Customer
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
}
4.业务Repository:CustomerRepository
public interface CustomerRepository extends CrudRepository<Customer, Long> {
}
测试下:
@SpringJUnitConfig(classes = JpaConfiguration.class)
public class JpaConfigurationTest {
@Autowired
private CustomerRepository repository;
@Test
@Transactional(readOnly = true)
public void testFindById(){
Customer customer = repository.findById(1L).orElse(null);
System.out.println(customer);
}
}
spring-data-jpa原理分析
spring-data-jpa是使用接口去访问数据库,很显然背后是动态代理实现的,这个跟mybatis用接口去访问数据库不能说是一模一样,基本上是大差不差的,知道了mybatis-spring的实现原理很容易就可以仿写一个spring-data-jpa。
第一步:添加EntityManager相关配置
跟之前一样,唯一的区别的我们自己来做组件扫描
@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = "com.example.demo",
excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {JpaConfiguration.class})})
public class MockJpaConfiguration {
}
第二步:做BeanDefinition扫描
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MockRepository {
}
@Component
public class RepositoryBeanDefinitionPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
RepositoryScanner scanner = new RepositoryScanner(beanDefinitionRegistry);
scanner.addIncludeFilter(new AnnotationTypeFilter(MockRepository.class));
scanner.scan("com.example.demo.dao");
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
}
public class RepositoryScanner extends ClassPathBeanDefinitionScanner {
public RepositoryScanner(BeanDefinitionRegistry registry) {
super(registry);
}
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return metadata.isIndependent() && ( metadata.isInterface());
}
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> bdhs = super.doScan(basePackages);
for(BeanDefinitionHolder bdh : bdhs){
GenericBeanDefinition bd = (GenericBeanDefinition)bdh.getBeanDefinition();
bd.getConstructorArgumentValues().addGenericArgumentValue(bd.getBeanClassName());
bd.setBeanClass(RepositoryFactoryBean.class);
}
return bdhs;
}
}
3.使用FactoryBean生成Repository代理
public class RepositoryFactoryBean implements FactoryBean, ApplicationContextAware {
private Class repositoryClass;
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public RepositoryFactoryBean(Class repositoryClass){
this.repositoryClass = repositoryClass;
}
@Override
public Object getObject() throws Exception {
return Proxy.newProxyInstance(repositoryClass.getClassLoader(),
new Class[]{repositoryClass},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("===========RepositoryFactoryBean===========");
ParameterizedType crudRepositoryClass = (ParameterizedType)repositoryClass.getGenericInterfaces()[0];
Class entityClass = Class.forName(crudRepositoryClass.getActualTypeArguments()[0].getTypeName());
EntityManagerFactory entityManagerFactory = applicationContext.getBean(EntityManagerFactory.class);
RepositoryProxy repositoryProxy = new RepositoryProxy(entityManagerFactory, entityClass);
Class repositoryProxyClass = repositoryProxy.getClass();
Method repositoryProxyMethod = repositoryProxyClass.getMethod(method.getName(), method.getParameterTypes());
return repositoryProxyMethod.invoke(repositoryProxy, args);
}
});
}
@Override
public Class<?> getObjectType() {
return repositoryClass;
}
}
4.静态代理RepositoryProxy
public class RepositoryProxy implements CrudRepository {
private EntityManager entityManager;
private Class entityClass;
public RepositoryProxy(EntityManagerFactory entityManagerFactory,
Class entityClass){
this.entityManager = entityManagerFactory.createEntityManager();
this.entityClass = entityClass;
}
@Override
public Optional findById(Object o) {
return Optional.of(entityManager.find(entityClass, o));
}
}
测试一下:
@SpringJUnitConfig(classes = MockJpaConfiguration.class)
public class MockJpaConfigurationTest {
@Autowired
private CustomerRepository repository;
@Test
@Transactional(readOnly = true)
public void testFindById(){
Customer customer = repository.findById(1L).orElse(null);
System.out.println(customer);
}
}
总结一下
- 1.扫描业务Repository接口,注册BeanDefinition
- 2.修改BeanDefinition的BeanClass是FactoryBean
- 3.FactoryBean创建业务Repository的动态代理类
- 4.动态代理类把对业务Repository接口的调用桥接到一个静态代理类中
- 5.静态代理类同样实现了Repository接口,内部使用EntityManager去访问数据库
|