SpringBoot+Jdbi快速上手

603次阅读
2条评论

共计 9705 个字符,预计需要花费 25 分钟才能阅读完成。

SpringBoot+Jdbi快速上手

前言

Jdbi是一款优秀的Java框架,具有以下特性

  • 构建于JDBC之上
  • 并不是ORM框架,但是提供了类ORM的功能
  • 不隐藏SQL,让使用者明白自己在做什么

总而言之,可以理解为JPA+Mybatis

正文

首先新建SpringBoot WebMvc项目,依赖如下

    implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.mysql:mysql-connector-j'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

这里我们添加了Mysql驱动同时使用spring-boot-starter-jdbc来帮助我们处理数据源的自动注册,接下来添加Jdbi相关依赖

注意笔者这里使用的Jdbi版本最低要求Java11,使用Java8的最后一个版本是3.39.1

    implementation 'org.jdbi:jdbi3-core:3.41.0'
    implementation 'org.jdbi:jdbi3-sqlobject:3.41.0'
    implementation 'org.jdbi:jdbi3-spring5:3.41.0'

配置Jdbi

@Configuration
public class JdbiConfiguration {

    @Bean
    public JdbiFactoryBean jdbiFactoryBean(DataSource dataSource) {
        return new JdbiFactoryBean(dataSource).setAutoInstallPlugins(true);
    }

    @Bean
    public JdbiPlugin sqlObjectPlugin() {
        return new SqlObjectPlugin();
    }
}

以上配置注册了Jdbi的核心

jdbi工具,我们操作的入口,同时开启了插件的自动注册,并添加了插件

数据源配置

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/java
spring.datasource.username=root
spring.datasource.password=root

DAO定义如下

public interface JdbiUserRepository {
    @SqlQuery("select name from user limit 1")
    String selectFirstName();
}

笔者就不提供初始化数据库脚本了,比较简单

接口定义及事务

@RestController
@RequiredArgsConstructor
public class JdbiController {
    private final Jdbi jdbi;

    @GetMapping(value = "/jdbi")
    @Transactional(rollbackFor = Exception.class)
    public String select() {
        return jdbi.onDemand(JdbiUserRepository.class).selectFirstName();
    }
}

到此演示了简单的Jdbi功能,更多的可以自行探索;看到这里,可能会有读者发现JdbiRepository实例好像并没有托管给Spring,没有实现复用。直接托管给Spring

    @Bean
    public JdbiUserRepository jdbiUserRepository(Jdbi jdbi) {
        return jdbi.onDemand(JdbiUserRepository.class);
    }

乍一看似乎没毛病,假如DAO膨胀起来了,难不成还一个个加?

浅入接管

手动添加不切实际,这里我们需要实现自动注册的逻辑

自定义Bean注解JdbiRepository
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface JdbiRepository {
}
FactoryBean实现JdbiRepositoryFactoryBean来构造我们的DAO
public class JdbiRepositoryFactoryBean<T> implements FactoryBean<T> {
    private Class<T> mapperInterface;
    private Jdbi jdbi;

    public JdbiRepositoryFactoryBean(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public T getObject() throws Exception {
        return getJdbi().onDemand(mapperInterface);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Class<T> getObjectType() {
        return this.mapperInterface;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isSingleton() {
        return true;
    }

    // ------------- mutators --------------

    public void setMapperInterface(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public Class<T> getMapperInterface() {
        return mapperInterface;
    }

    public Jdbi getJdbi() {
        return jdbi;
    }

    public void setJdbi(Jdbi jdbi) {
        this.jdbi = jdbi;
    }
}
JdbiRepositoryClassPathBeanDefinitionScanner扫描候选的DAO
public class JdbiRepositoryClassPathBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
    static final String FACTORY_BEAN_OBJECT_TYPE = "factoryBeanObjectType";

    private Class<? extends Annotation> annotationClass;

    @SuppressWarnings("rawtypes")
    private final Class<? extends JdbiRepositoryFactoryBean> factoryBeanClass = JdbiRepositoryFactoryBean.class;

    public JdbiRepositoryClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
        super(registry, false);
    }


    public void setAnnotationClass(Class<? extends Annotation> annotationClass) {
        this.annotationClass = annotationClass;
    }

    public void registerFilters() {
        boolean acceptAllInterfaces = true;

        if (this.annotationClass != null) {
            addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
            acceptAllInterfaces = false;
        }

        if (acceptAllInterfaces) {
            // default include filter that accepts all classes
            addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
        }

        // exclude package-info.java
        addExcludeFilter((metadataReader, metadataReaderFactory) -> {
            String className = metadataReader.getClassMetadata().getClassName();
            return className.endsWith("package-info");
        });
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @NonNull
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        if (!beanDefinitions.isEmpty()) {
            processBeanDefinitions(beanDefinitions);
        }
        return beanDefinitions;
    }

    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        AbstractBeanDefinition definition;
        for (BeanDefinitionHolder holder : beanDefinitions) {
            definition = (AbstractBeanDefinition) holder.getBeanDefinition();
            boolean scopedProxy = false;
            if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {
                definition = (AbstractBeanDefinition) Optional
                        .ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition())
                        .map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException(
                                "The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]"));
                scopedProxy = true;
            }
            String beanClassName = definition.getBeanClassName();
            definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
            definition.setBeanClass(this.factoryBeanClass);
            definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
            definition.setLazyInit(true);

            if (scopedProxy) {
                continue;
            }
            definition.setScope(ConfigurableBeanFactory.SCOPE_SINGLETON);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean checkCandidate(@NonNull String beanName, @NonNull BeanDefinition beanDefinition) {
        return super.checkCandidate(beanName, beanDefinition);
    }

}
JdbiRepositoryBeanDefinitionRegistryPostProcessor启用扫描
public class JdbiRepositoryBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
    private String basePackage;
    private Class<? extends Annotation> annotationClass;
    private ApplicationContext applicationContext;
    private String beanName;

    public void setBasePackage(String basePackage) {
        this.basePackage = basePackage;
    }

    public void setAnnotationClass(Class<? extends Annotation> annotationClass) {
        this.annotationClass = annotationClass;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setBeanName(@NonNull String name) {
        this.beanName = name;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        notNull(this.basePackage, "Property 'basePackage' is required");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void postProcessBeanFactory(@NonNull ConfigurableListableBeanFactory beanFactory) {
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void postProcessBeanDefinitionRegistry(@NonNull BeanDefinitionRegistry registry) {
        processPropertyPlaceHolders();
        JdbiRepositoryClassPathBeanDefinitionScanner scanner = new JdbiRepositoryClassPathBeanDefinitionScanner(registry);
        scanner.setAnnotationClass(this.annotationClass);
        scanner.setResourceLoader(this.applicationContext);
        scanner.registerFilters();
        scanner.scan(
                StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
    }

    private void processPropertyPlaceHolders() {
        Map<String, PropertyResourceConfigurer> propertyResourceConfigurerMap = applicationContext.getBeansOfType(PropertyResourceConfigurer.class,
                false, false);
        if (!propertyResourceConfigurerMap.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {
            BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext).getBeanFactory()
                    .getBeanDefinition(beanName);

            DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
            factory.registerBeanDefinition(beanName, mapperScannerBean);

            for (PropertyResourceConfigurer prc : propertyResourceConfigurerMap.values()) {
                prc.postProcessBeanFactory(factory);
            }

            PropertyValues values = mapperScannerBean.getPropertyValues();

            this.basePackage = getPropertyValue("basePackage", values);
        }
        this.basePackage = Optional.ofNullable(this.basePackage).map(getEnvironment()::resolvePlaceholders).orElse(null);
    }

    private Environment getEnvironment() {
        return this.applicationContext.getEnvironment();
    }

    private String getPropertyValue(String propertyName, PropertyValues values) {
        PropertyValue property = values.getPropertyValue(propertyName);

        if (property == null) {
            return null;
        }

        Object value = property.getValue();

        if (value == null) {
            return null;
        } else if (value instanceof String) {
            return value.toString();
        } else if (value instanceof TypedStringValue) {
            return ((TypedStringValue) value).getValue();
        } else {
            return null;
        }
    }

}
JdbiImportBeanDefinitionRegistrar注册入口

笔者这里指定了包名org.flmelody.dao用来扫描,该包名会一路透传到扫描器

public class JdbiImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {


    @Override
    public void registerBeanDefinitions(@NonNull AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(JdbiRepositoryBeanDefinitionRegistryPostProcessor.class);
        builder.addPropertyValue("annotationClass", JdbiRepository.class);
        builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(Collections.singleton("org.flmelody.dao")));
        registry.registerBeanDefinition(JdbiRepositoryBeanDefinitionRegistryPostProcessor.class.getName(), builder.getBeanDefinition());
    }
}
添加自动配置
@Configuration
@Import(value = JdbiImportBeanDefinitionRegistrar.class)
public class JdbiConfiguration {

    @Bean
    public JdbiFactoryBean jdbiFactoryBean(DataSource dataSource) {
        return new JdbiFactoryBean(dataSource).setAutoInstallPlugins(true);
    }

    @Bean
    public JdbiPlugin sqlObjectPlugin() {
        return new SqlObjectPlugin();
    }
}

接下来我们就可以像往常一样使用JdbiUserRepository这类Bean了

小结

本文演示了SpringBoot和Jdbi的上手示例,并着重阐述了如何让Spring接管Jdbi的代理。Jdbi作为优秀的Java框架,API丰富,灵活易用。

正文完
 
mysteriousman
版权声明:本站原创文章,由 mysteriousman 2023-08-25发表,共计9705字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(2条评论)
mysteriousman 博主
2023-08-27 12:33:14 回复

快速开始🚀

<dependency>
    <groupId>org.flmelody</groupId>
    <artifactId>spring-factory-bean-jdbi</artifactId>
    <version>1.0.0-RELEASE</version>
</dependency>

详细文档请移步👉github👈

 Linux  Chrome  英国
    mysteriousman 博主
    2023-08-27 14:05:50 回复

    自定义🤔

    <dependency>
        <groupId>org.flmelody</groupId>
        <artifactId>spring-factory-bean-core</artifactId>
        <version>1.0.0-RELEASE</version>
    </dependency>
     Linux  Chrome  英国