mybatis-plus使用oracle序列生成器不生效问题

mybatis-plus是一个很好的mybatis增强工具,主要因为其CRUD操作可以减少很多代码的编写,提高开发效率,因此引入了现有项目。有兴趣同学可以查看官方文档学习:https://baomidou.com/

0. 背景介绍
  1. 项目是springboot 2.1.6项目,且有多个数据源,每个数据源都需要spring事务管理
  2. 本篇基于mybatis-plus 3.3.0
  3. 如下为其中一个数据源的事务管理配置
@Configuration
@MapperScan(basePackages = "com.zeng.reach.mapper.test", sqlSessionTemplateRef = "sqlSessionTemplateTest")
public class TestDataConfig {
    private static final Logger LOG = LoggerFactory.getLogger(TestDataConfig.class);

    @Bean(name = "dataSourceTest", initMethod = "init", destroyMethod = "close")
    @Primary
    @ConfigurationProperties(prefix = "test.datasource")
    public DruidDataSource dataSourceTest() {
        return new DruidDataSource();
    }

    @Bean
    public GlobalConfig globalConfig() {
        GlobalConfig conf = new GlobalConfig();
        conf.setDbConfig(new GlobalConfig.DbConfig().setKeyGenerator(new OracleKeyGenerator()));
        return conf;
    }

    @Bean(name = "sqlSessionFactoryTest")
    @Primary
    @DependsOn("globalConfig")
    public MybatisSqlSessionFactoryBean sqlSessionFactoryTest(@Qualifier("dataSourceTest") DruidDataSource dataSource) throws IOException {
        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        bean.setMapperLocations(resolver.getResources("classpath*:mapper/test/*Mapper.xml"));


        return bean;
    }



    @Bean(name = "sqlSessionTemplateTest")
    @Primary
    public SqlSessionTemplate sqlSessionTemplateTest(@Qualifier("sqlSessionFactoryTest") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }



    @Bean(name = {"transactionManagerTest", "test"})
    @Primary
    public DataSourceTransactionManager dataSourceTransactionManager(@Qualifier("dataSourceTest") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

}

1. 问题描述

插入数据时,抛出异常,异常堆栈(有截取)如下:

2020-11-30 10:14:41:418[DEBUG][druid.sql.Statement][18a88c4d-cba6-4adb-af08-a5f1090c955a]-{conn-10010, pstmt-20001} created. INSERT INTO tbl_task  ( id,
no,
receive_time,
min,
code,
success_status,
status,
info,
source )  VALUES  ( ?,
?,
?,
?,
?,
?,
?,
?,
? )
2020-11-30 10:14:41:420[DEBUG][com.zeng.reach.mapper.test.TaskMapper.insert][18a88c4d-cba6-4adb-af08-a5f1090c955a]-==> Parameters: 0(Long), 11111111(String), 2020-11-30 10:14:38.74(Timestamp), 1(Integer), box-1222(String), 1(Integer), 1(Integer), dffsf(String), dsfs(String)

java.sql.SQLException: ORA-00001: 违反唯一约束条件 (TEST.PK_TBL_TASK_ID)

	at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:445)
	at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:396)
	at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:879)
	at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:450)
	at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:192)
	at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:531)

我们可以看到打印的sql,主键id为0,经过几次测试,都是0。主键是oracle的序列,所以确定是序列没有获取到。

2. 解决方案
@Configuration
@MapperScan(basePackages = "com.zeng.reach.mapper.test", sqlSessionTemplateRef = "sqlSessionTemplateTest")
public class TestDataConfig {
    private static final Logger LOG = LoggerFactory.getLogger(TestDataConfig.class);

    @Bean(name = "dataSourceTest", initMethod = "init", destroyMethod = "close")
    @Primary
    @ConfigurationProperties(prefix = "test.datasource")
    public DruidDataSource dataSourceTest() {
        return new DruidDataSource();
    }

    @Bean
    public GlobalConfig globalConfig() {
        GlobalConfig conf = new GlobalConfig();
        conf.setDbConfig(new GlobalConfig.DbConfig().setKeyGenerator(new OracleKeyGenerator()));
        return conf;
    }

    @Bean(name = "sqlSessionFactoryTest")
    @Primary
    @DependsOn("globalConfig")
    public MybatisSqlSessionFactoryBean sqlSessionFactoryTest(@Qualifier("dataSourceTest") DruidDataSource dataSource) throws IOException {
        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        bean.setMapperLocations(resolver.getResources("classpath*:mapper/test/*Mapper.xml"));
        //注意:只增加了这一行
 		bean.setGlobalConfig(globalConfig());

        return bean;
    }



    @Bean(name = "sqlSessionTemplateTest")
    @Primary
    public SqlSessionTemplate sqlSessionTemplateTest(@Qualifier("sqlSessionFactoryTest") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }



    @Bean(name = {"transactionManagerTest", "test"})
    @Primary
    public DataSourceTransactionManager dataSourceTransactionManager(@Qualifier("dataSourceTest") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

}

3、 分析原因
  1. 按照官方配置,配置了全局配置bean: GlobalConfig,如上背景交代。

  2. 经过上面异常分析,猜测GlobalConfig中OracleKeyGenerator是否配置有问题。

  3. 调试源码OracleKeyGenerator,如下(debug发现下面的代码没有执行):

public class OracleKeyGenerator implements IKeyGenerator {

    @Override
    public String executeSql(String incrementerName) {
        return "SELECT " + incrementerName + ".NEXTVAL FROM DUAL";
    }
}
  1. 反向查询调用链,到Insert源码中,如下分析:
 public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        KeyGenerator keyGenerator = new NoKeyGenerator();
        SqlMethod sqlMethod = SqlMethod.INSERT_ONE;
        String columnScript = SqlScriptUtils.convertTrim(tableInfo.getAllInsertSqlColumnMaybeIf(),
            LEFT_BRACKET, RIGHT_BRACKET, null, COMMA);
        String valuesScript = SqlScriptUtils.convertTrim(tableInfo.getAllInsertSqlPropertyMaybeIf(null),
            LEFT_BRACKET, RIGHT_BRACKET, null, COMMA);
        String keyProperty = null;
        String keyColumn = null;
        // 表包含主键处理逻辑,如果不包含主键当普通字段处理
        if (StringUtils.isNotBlank(tableInfo.getKeyProperty())) {
            if (tableInfo.getIdType() == IdType.AUTO) {
                /** 自增主键 */
                keyGenerator = new Jdbc3KeyGenerator();
                keyProperty = tableInfo.getKeyProperty();
                keyColumn = tableInfo.getKeyColumn();
            } else {
            //如果我们实体映射的tableInfo中设置的@KeySequence不为空,此处即可获取项目中配置的OracleKeyGenerator(但此处,我们发现null == tableInfo.getKeySequence())
                if (null != tableInfo.getKeySequence()) {
                
                    keyGenerator = TableInfoHelper.genKeyGenerator(getMethod(sqlMethod), tableInfo, builderAssistant);
                    keyProperty = tableInfo.getKeyProperty();
                    keyColumn = tableInfo.getKeyColumn();
                }
            }
        }
        String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addInsertMappedStatement(mapperClass, modelClass, getMethod(sqlMethod), sqlSource, keyGenerator, keyProperty, keyColumn);
    }
  1. 经过调试,我们最终确定tableInfo中的KeySequence,是从MybatisSqlSessionFactoryBean的GlobalConfig中DbConfig中IKeyGenerator中获取。(其实从官网GlobalConfig配置Bean的代码中也可以看出来)

  2. 但是我们是直接用@Bean的方式去实例化GlobalConfig,而MybatisSqlSessionFactoryBean中需要以set方式设置GlobalConfig,所以需要我们手动设置GlobalConfig。com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean源码如下:

    public class MybatisSqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(MybatisSqlSessionFactoryBean.class);
    
        // TODO 自定义枚举包
        @Setter
        private String typeEnumsPackage;
    
        // TODO 自定义全局配置
        @Setter
        private GlobalConfig globalConfig;
    }
    
4. 总结
  1. 由于连接多个数据源,需要事务管理;没有采用mybatis-plus推荐的springboot配置方式,而是使用的springmvc方式。这种方式会导致MybatisSqlSessionFactoryBean中增强的属性值都需要手动编码set设值进去
  2. 如果没有用mybatis-plus springboot自动装配的SqlSessionFactory,通过application.yml配置的MybatisProperties属性都是无效的。如下MybatisAutoConfiguration源码中:sqlSessionFactory方法不会执行,其中的factory.setxxx设置自然都是不会执行;sqlSessionTemplate方法同理。这就是有的同学疑惑为什么配置了application.yml但没有生效的原因,有兴趣可以去了解springboot starter原理(有空写一篇解释)
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration {

  private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);

  private final MybatisProperties properties;

  @Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
    if (StringUtils.hasText(this.properties.getConfigLocation())) {
      factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }
    Configuration configuration = this.properties.getConfiguration();
    if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
      configuration = new Configuration();
    }
    if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
      for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
        customizer.customize(configuration);
      }
    }
    factory.setConfiguration(configuration);
    if (this.properties.getConfigurationProperties() != null) {
      factory.setConfigurationProperties(this.properties.getConfigurationProperties());
    }
    if (!ObjectUtils.isEmpty(this.interceptors)) {
      factory.setPlugins(this.interceptors);
    }
    if (this.databaseIdProvider != null) {
      factory.setDatabaseIdProvider(this.databaseIdProvider);
    }
    if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
      factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
    }
    if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
      factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
    }
    if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
      factory.setMapperLocations(this.properties.resolveMapperLocations());
    }

    return factory.getObject();
  }

}
  • 8
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
您好!关于使用mybatis-plusoracle进行分页的问题,您可以参考以下步骤: 1. 在pom.xml文件中添加mybatis-plusoracle的依赖: ```xml <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>3.4.3</version> </dependency> <dependency> <groupId>com.oracle.jdbc</groupId> <artifactId>ojdbc8</artifactId> <version>19.3.0.0</version> </dependency> ``` 2. 在配置文件中配置mybatis-plus的分页插件: ```yaml mybatis-plus: mapper-locations: classpath*:mapper/*Mapper.xml global-config: db-config: id-type: auto table-capital: true column-capital: false logic-delete-value: 1 logic-not-delete-value: 0 logic-delete-field: delete_flag pageSize: 10 # 每页大小 page: 1 # 当前页 overflow: true # 是否溢出,如果为true,则返回最后一页,如果为false,则返回第一页 limit: 100 #最大分页限制 configuration: map-underscore-to-camel-case: true cache-enabled: false log-impl: org.apache.ibatis.logging.stdout.StdOutImpl plugins: - component: com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor ``` 3. 在mapper接口中定义查询方法,并且使用Page对象保存分页参数: ```java public interface UserMapper extends BaseMapper<User> { List<User> selectUserList(Page<User> page, @Param("search") String search); } ``` 4. 在service层中调用mapper接口的方法,同时传入页码和每页大小的参数: ```java @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public IPage<User> selectUserList(Page<User> page, String search) { return userMapper.selectUserList(page, search); } } ``` 使用以上方法可以轻松实现mybatis-plusoracle的分页功能,希望对您有所帮助。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值