IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> spring-data-jpa使用与原理解析 -> 正文阅读

[Java知识库]spring-data-jpa使用与原理解析

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")//启用repository扫描
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;
    }
    /**
	 * 定义EntityManagerFactoryBean
	 */
    @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;
    }
    /**
	 * 定义TransactionManager
	 */
    @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
// 去掉Repositories扫描
// @EnableJpaRepositories("com.example.demo.dao")
//手动做组件扫描,要把上一步的JpaConfiguration排除掉
//但是因为CustomerRepository是接口,Spring默认不会把它注册到容器,所以需要修改BeanDefinition的注册
@ComponentScan(basePackages = "com.example.demo",
        excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {JpaConfiguration.class})})
public class MockJpaConfiguration {
  // ...省略Bean的定义
}

第二步:做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);
        //扫描添加了MockRepository注解的bean
        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){
        	// 扫描出来的实际是个接口,因此是无法实例化的
        	// 因此这里可以返回一个FactoryBean来构造要生成的bean
        	// 因此修改BeanDefinition的类型是RepositoryFactoryBean
        	// 同时把Repository的Class作为入参传递进去
            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实现了Repository接口,可以看成是一个静态代理,在这里面使用EntityManager去真正访问数据库,当调用CustomerRepository上的方法的时候,拿到被调用的方法名,转而去调用RepositoryProxy的同名的方法即可。
                        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) {
    	// 这里去调用EntityManager
        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去访问数据库
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-05-09 12:26:14  更:2022-05-09 12:29:30 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 0:29:07-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码