共计 9705 个字符,预计需要花费 25 分钟才能阅读完成。
前言
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丰富,灵活易用。
正文完
快速开始🚀
详细文档请移步👉github👈
自定义🤔