SpringBoot + Vue 后台管理系统(五):mybatis-plus批量插入引发的问题PersistenceException

什么是MyBatis-Plus?

翻译过来就是“加强的mybatis”;封装了各种方法,形成了类似于JPA的使用方式。有兴趣的朋友参考文档:MyBatis-Plus

SpringBoot集合方式

1.添加以下依赖(注:包含MyBatis的依赖)

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-generator</artifactId>
        <version>3.1.0</version>
    </dependency> 
本文使用的是druid + mysql 的多数据源;所以在pom文件导入依赖
    <!-- 数据库关联 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.10</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
自动配置
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
配置文件
spring: 
  profiles:
    active: dev
  datasource: 
    url: jdbc:mysql://lcoalhost:3306/db_comm_01?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 12345678
    type: com.alibaba.druid.pool.DruidDataSource
  member: 
    datasource: 
      url: jdbc:mysql://lcoalhost:3306/db_member_01?useUnicode=true&characterEncoding=utf-8&useSSL=false
      username: root
      password: 12345678
      type: com.alibaba.druid.pool.DruidDataSource 
  oauth: 
    datasource: 
      url: jdbc:mysql://lcoalhost:3306/oauth2?useUnicode=true&characterEncoding=utf-8&useSSL=false
      username: root
      password: 12345678
      type: com.alibaba.druid.pool.DruidDataSource 
  redis:
    database: 0
    host: lcoalhost
    port: 6379
    password: 12345678

2.配置

数据源DataSourceConfig.java
@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    DataSource dsComm() {
        return DruidDataSourceBuilder.create().build();
    }
    @Bean
    @ConfigurationProperties(prefix = "spring.member.datasource")
    DataSource dsMember() {
        return DruidDataSourceBuilder.create().build();
    }
    @Bean
    @ConfigurationProperties(prefix = "spring.oauth.datasource")
    DataSource dsOauth() {
        return DruidDataSourceBuilder.create().build();
    }
}
MyBatis-Plus配置MyBatisPlusConfig.java
@Configuration
public class MybatisPlusConfig {
    /**   mybatis-plus分页插件<br>  */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        // 设置数据库方言  
       paginationInterceptor.setDialectType(DbType.MYSQL.getDb());
        return paginationInterceptor;
    } 
}
多数据源分开配置SqlSessionFactory等Bean
//只要拷贝多个这个文件即可处理多数据源的问题,根据不同的数据源创建相应的包分开放Entity,Mapper,Repository等类即可。
@Configuration
@MapperScan(basePackages = "bertram.wang.domain.mapper.comm", 
            sqlSessionFactoryRef = "sqlSessionFactoryComm", 
            sqlSessionTemplateRef = "sqlSessionTemplateComm")
public class MyBatisConfigCommTest { 
    @Resource(name = "dsComm")
    DataSource dsComm;
    //配置事务管理器
    @Primary
    @Bean(name = "dsCommTransaction")
    PlatformTransactionManager dsMemberTransaction() {
        return new DataSourceTransactionManager(dsComm);
    } 
    
    @Bean
    SqlSessionFactory sqlSessionFactoryComm() {
        SqlSessionFactory sessionFactory = null;
        try {
            MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
            sqlSessionFactory.setDataSource(dsComm);
            sqlSessionFactory.setTypeAliasesPackage("bertram.wang.domain.entity.comm");
            sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/comm/*Mapper.xml"));
            MybatisConfiguration configuration = new MybatisConfiguration();
            configuration.setJdbcTypeForNull(JdbcType.NULL);
            configuration.setMapUnderscoreToCamelCase(true);
            sqlSessionFactory.setConfiguration(configuration); 
            sessionFactory =  sqlSessionFactory.getObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sessionFactory;
    }
    
    @Bean
    SqlSessionTemplate sqlSessionTemplateComm() {
        return new SqlSessionTemplate(sqlSessionFactoryComm());
    }
}
文档结构示例

3.使用

实体示例: User extends BaseEntity (主动填充后面会介绍)
@Data
@Accessors(chain = true)
public class BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 主键标识,自动增长
     */
    @TableId(type = IdType.AUTO)
    private Integer id;
    
    /**
     * 创建日期,添加时自动填充当前日期
     */
    @TableField(fill = FieldFill.INSERT)
    private Date createDate;
    
    /**
     * 修改日期,添加和修改自动填充当前日期
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date modifyDate;
}
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("sys_user")
public class User extends BaseEntity {

    private static final long serialVersionUID = 1L;
    /**
     * *角色ID数组
     */
    private transient List<Integer> roleIds;

    /**
     * *昵称,限制16字符
     */
    private String name;
    /**
     * *用户密码(MD5加密)
     */
    private String password;

}
Mapper示例: 没有自实现的方法
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
Repository以其实现示例
public interface UserRepository extends IService<User> {
} 
@Repository
public class UserRepositoryImpl extends ServiceImpl<UserMapper, User> implements UserRepository { 
}
测试单元
// Select
@Test
public void () throws Exception { 
    User user = userRepository.getById(1);
    log.info("users:{}", page.getRecords()); 
}
执行日志

执行的SQL很简单: select * from user where id = 1;(忽略通配符)
再看上面的logo。和springboot一样自带logo牛逼哄哄的样子。springboot打印logo是可以关闭:app.setBannerMode(Banner.Mode.OFF);
以上是用了mybatis-plus默认配置。

mybatis-plus配置字段及意义
mybatis-plus:
  config-location: xxxxxxx  # MyBatis 配置文件位置  ---String 默认:null 
  mapper-locations: # MyBatis Mapper 所对应的 XML 文件位置  ---String[]  默认: [] 
  - classpath*:/mapper/comm
  - classpath*:/mapper/member
  type-aliases-package: bertram.wang.domain.entity # 实体包   --- String
  type-aliases-super-type: bertram.wang.domain.entity.BaseEntity # 实体的父类类名 Class<?>
  type-handlers-package: xxxxxxxx # SqlSessionFactoryBean 会把该包下面的类注册为对应的 TypeHandler ---String 默认:null 
  typeEnumsPackage: xxxxx # 让实体类字段能够简单快捷的使用枚举属性  ---String 默认:null 
  check-config-location: false # 检查mybatis xml 文件的存在
  executor-type: simple # 知道mybatis的执行器
  # configuration-properties:  # 指定外部化 MyBatis Properties 配置,通过该配置可以抽离配置,实现不同环境的配置部署
  configuration: # 本部分(Configuration)的配置大都为 MyBatis 原生支持的配置
    map-underscore-to-camel-case: true #  是否开启自动驼峰命名规则(camel case)映射
    default-enum-type-handler: org.apache.ibatis.type.EnumTypeHandler 
    aggressive-lazy-loading: true #当设置为 true 的时候,懒加载的对象可能被任何懒属性全部加载,否则,每个属性都按需加载。需要和 lazyLoadingEnabled 一起使用
    auto-mapping-behavior: partial # 自动映射策略
    auto-mapping-unknown-column-behavior: none # 自动映射遇到未知列时的处理策略
    cache-enabled: true # 映射器缓存开启
    call-setters-on-nulls: false # 空值的列
    configuration-factory: xxxxxxx # 指定一个提供 Configuration 实例的工厂类 (从 3.2.3 版本开始)
  global-config:
    banner: true # 控制台 print mybatis-plus 的 LOGO
    sql-parser-cache: true # 开启缓存
    worker-id: 1234312 # ID的部分值
    datacenter-id: 1313 # 数据标识ID部分
    super-mapper-class: com.baomidou.mybatisplus.core.mapper.Mapper.class # 通用Mapper父类
    db-config:
      db-type: mysql # 数据库类型
      id-type: auto  # id策略
      table-prefix: t_ # 表前缀
      table-underline: true # 表明下划线命名
      column-like: false # 是否开启 LIKE 查询,即根据 entity 自动生成的 where 条件中 String 类型字段 是否使用 LIKE,默认不开启
      capital-mode: false # 不开启大写命名
      logic-delete-value: 1 # 逻辑已删除值
      logic-not-delete-value: 0 #逻辑未删除值
添加以下配置到配置文件:
mybatis-plus:
  global-config:
    banner: false # 控制台 print mybatis-plus 的 LOGO
    sql-parser-cache: true # 开启缓存
    db-config:
      db-type: mysql # 数据库类型
      id-type: auto  # id策略
      table-underline: true # 表明下划线命名    

***执行发现并没有生效???????

image.png

没生效,那就自己注册一个Bean呗! MyBatisPlusConfig.java添加Bean

// 和上面配置文件同样的属性
@Bean
public GlobalConfig globalConfig() {
    GlobalConfig globalConfig = new GlobalConfig();
    globalConfig.setBanner(false);
    globalConfig.setSqlParserCache(true);
    DbConfig dbConfig = new GlobalConfig.DbConfig();
    dbConfig.setDbType(DbType.MYSQL);
    dbConfig.setIdType(IdType.AUTO);
    dbConfig.setTableUnderline(true);
    return globalConfig;
}

MyBatisConfigComm.java 中使用

@Autowired
GlobalConfig globalConfig;
//  在SqlSessionFactory Bean生成时添加以下代码。
sqlSessionFactory.setGlobalConfig(globalConfig);
执行生效

没有打印logo说明注入的Bean生效了。
如果就这样结束了那就不怎么坑了。在项目中insert, insertBatch操作!

Insert插入一条数据

公用字段创建时间和修改时间其实使用的都是当前时间。如果能自动填充方便一些。强大的mybatis-plus提供了自动填充功能。具体参考:自动填充功能
根据官方说的创建MyMetaObjectHandler,实体有加注解。

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    
    private static final Logger LOGGER = LoggerFactory.getLogger(MyMetaObjectHandler.class);

    @Override
    public void insertFill(MetaObject metaObject) {
        LOGGER.info("start insert fill ....");
        this.setInsertFieldValByName("createDate", new Date(), metaObject);
        this.setInsertFieldValByName("modifyDate", new Date(), metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        LOGGER.info("start update fill ....");
        this.setUpdateFieldValByName("modifyDate", new Date(), metaObject);
    }
}

执行单元测试

@Test
public void test2() {
    log.info("==============测试开始==================");
    User user = new User();
    user.setName("bertram.wang");
    user.setPassword("password_bertram.wang");
    userRepository.save(user);
    log.info("==============测试结束==================");
}
结果很明显并没有用

在看配置的时候就发现GlobalConfig下有个 MetaObjectHandler属性,只是告诉我们说注入Bean即可。既然没用那就set进去就好了

globalConfig.setMetaObjectHandler(myMetaObjectHandler());
成功生效

看到这里应该万事大吉了。

InsertBatch 批量插入

@Test
public void test3() {
    log.info("==============测试开始==================");
    List<User> users = new ArrayList<>();
    for(int i=0; i<3; i++) {
        User user = new User();
        user.setName("name_"+i);
        user.setPassword("password_"+i);
        users.add(user);
    }
    userRepository.saveBatch(users);
    log.info("==============测试结束==================");
}
执行异常

org.apache.ibatis.exceptions.PersistenceException: ...Mapped Statements collection does not contain value for bertram.wang.domain.mapper.comm.UserMapper.insert????

异常一直没有找到处理的方式。 求大神指教!!!! 换了一个方式处理。

自动配置不会存在这个异常

很好。把刚才一系列的操作删除换回原始的自动配置的GlobalConfig


发现现在确实没有抛出以上异常

异常虽然没有了又回到怎么设置自动填充的问题了?
数据库设置成默认当前时间。每次修改都设置一下修改时间。这样可以解决。(不建议)

处理方案

找到当前的自动配置GlobalConfig信息,然后再手动set进去。
MyBatisConfigComm.java 方法 sqlSessionFactoryComm 添加一下代码:

        // 获得前配置的全局变量修改
        GlobalConfig globalConfig = GlobalConfigUtils.getGlobalConfig(configuration);
        globalConfig.setBanner(false);
        globalConfig.setMetaObjectHandler(myMetaObjectHandler);
        globalConfig.setSqlInjector(logicSqlInjector);
        globalConfig.getDbConfig().setDbType(DbType.MYSQL);
        globalConfig.getDbConfig().setIdType(IdType.AUTO);
        globalConfig.getDbConfig().setColumnLike(true);
        sqlSessionFactory.setGlobalConfig(globalConfig);
最后的 MyBatisConfigComm.java完成示例:
@Configuration
@MapperScan(basePackages = "bertram.wang.domain.mapper.comm", sqlSessionFactoryRef = "sqlSessionFactoryComm", sqlSessionTemplateRef = "sqlSessionTemplateComm")
public class MyBatisConfigComm {
    @Autowired
    MyMetaObjectHandler myMetaObjectHandler;
    @Autowired
    LogicSqlInjector logicSqlInjector;
    @Resource(name = "dsComm")
    DataSource dsComm;
    //配置事务管理器
    @Primary
    @Bean(name = "dsCommTransaction")
    PlatformTransactionManager dsMemberTransaction() {
        return new DataSourceTransactionManager(dsComm);
    } 
    
    @Bean
    SqlSessionFactory sqlSessionFactoryComm() {
        SqlSessionFactory sessionFactory = null;
        try {
            MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
            sqlSessionFactory.setDataSource(dsComm);
            sqlSessionFactory.setTypeAliasesPackage("bertram.wang.domain.entity.comm");
            sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/comm/*Mapper.xml"));
            MybatisConfiguration configuration = new MybatisConfiguration();
            configuration.setJdbcTypeForNull(JdbcType.NULL);
            configuration.setMapUnderscoreToCamelCase(true);
            sqlSessionFactory.setConfiguration(configuration);
            // 获得前配置的全局变量修改
            GlobalConfig globalConfig = GlobalConfigUtils.getGlobalConfig(configuration);
            globalConfig.setBanner(false);
            globalConfig.setMetaObjectHandler(myMetaObjectHandler);
            globalConfig.setSqlInjector(logicSqlInjector);
            globalConfig.getDbConfig().setDbType(DbType.MYSQL);
            globalConfig.getDbConfig().setIdType(IdType.AUTO);
            globalConfig.getDbConfig().setColumnLike(true);
            sqlSessionFactory.setGlobalConfig(globalConfig);
            
            sessionFactory =  sqlSessionFactory.getObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sessionFactory;
    }
    
    @Bean
    SqlSessionTemplate sqlSessionTemplateComm() {
        return new SqlSessionTemplate(sqlSessionFactoryComm());
    }
} 

替换前面的这个文件即可。

执行成功
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,980评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,422评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,130评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,553评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,408评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,326评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,720评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,373评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,678评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,722评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,486评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,335评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,738评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,283评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,692评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,893评论 2 335

推荐阅读更多精彩内容