MybatisPlus拓展篇

news2024/9/27 9:25:31

文章目录

  • 逻辑删除
  • 通用枚举
  • 字段类型处理器
  • 自动填充功能
  • 防全表更新与删除插件
  • MybatisX快速开发插件
    • 插件安装
    • 逆向工程
    • 常见需求代码生成
  • 乐观锁
    • 问题引入
    • 乐观锁的使用
    • 效果测试
  • 代码生成器
  • 执行SQL分析打印
  • 多数据源

逻辑删除

  • 逻辑删除的操作就是增加一个字段表示这个数据的状态,如果一条数据需要删除,我们通过改变这条数据的状态来实现,这样既可以表示这条数据是删除的状态,又保留了数据以便以后统计。
  1. 在表中增加一列字段,表示是否删除的状态,这里使用的字段类型为int类型,通过1表示该条数据可用,0表示该条数据不可用。
    在这里插入图片描述
  2. 实体类添加一个字段为Integer,用于对应表中的字段
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User extends Model<User> {
        private Long id;
        private String name;
        private Integer age;
    	private String email;
        @TableLogic(value = "1",delval = "0")
        private Integer status;
    }
    
  3. 测试逻辑删除效果
@Test
void logicDelete(){
    userMapper.deleteById(7L);
}

在这里插入图片描述
在这里插入图片描述

  • 还可以通过全局配置来实现逻辑删除的效果
    在这里插入图片描述

通用枚举

  • 当想要表示一组信息,这组信息只能从一些固定的值中进行选择,不能随意写,在这种场景下,枚举就非常的合适。
  1. 在表中添加一个字段,表示性别,使用int来描述,因为int类型可以通过0和1这两个值来表示两个不同的性别
    在这里插入图片描述
  2. 编写枚举类
public enum GenderEnum {

    MAN(0,"男"),
    WOMAN(1,"女");
    
	@EnumValue
    private Integer gender;
    private String genderName;

    GenderEnum(Integer gender, String genderName) {
        this.gender = gender;
        this.genderName = genderName;
    }
}
  1. 实体类添加相关字段
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User extends Model<User> {
    private Long id;
    private String name;
    private Integer age;
    private String email;
    private GenderEnum gender;
    private Integer status;
}
  1. 添加数据
@Test
void enumTest(){
    User user = new User();
    user.setName("liu");
    user.setAge(29);
    user.setEmail("liu@powernode.com");
    user.setGenderEnum(GenderEnum.MAN);
    user.setStatus(1);
    userMapper.insert(user);
}

在这里插入图片描述

字段类型处理器

  • 在某些场景下,在实体类中是使用Map集合作为属性接收前端传递过来的数据的,但是这些数据存储在数据库时,使用的是json格式的数据进行存储,json本质是一个字符串,就是varchar类型。那怎么做到实体类的Map类型和数据库的varchar类型的互相转换,这里就需要使用到字段类型处理器来完成。
  1. 实体类
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User extends Model<User> {
        private Long id;
        private String name;
        private Integer age;
        private String email;
        private GenderEnum gender;
        private Integer status;
        private Map<String,String> contact;//联系方式
    }
    
  2. 在数据库中添加一个字段,为varchar类型
    在这里插入图片描述
  3. 为实体类添加上对应的注解,实现使用字段类型处理器进行不同类型数据转换
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @TableName(autoResultMap = true)//查询时将json字符串封装为Map集合
    public class User extends Model<User> {
        private Long id;
        private String name;
        private Integer age;
        private String email;
        private GenderEnum gender;
        private Integer status;
        @TableField(typeHandler = FastjsonTypeHandler.class)//指定字段类型处理器
        private Map<String,String> contact;//联系方式
    }
    
  4. 引入字段类型处理器依赖Fastjson
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.76</version>
    </dependency>
    

自动填充功能

  • 在项目中有一些属性,如果我们不希望每次都填充的话,我们可以设置为自动填充,比如常见的时间,创建时间和更新时间可以设置为自动填充。
  1. 在实体类中,添加对应字段,并为需要自动填充的属性指定填充时机
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName(autoResultMap = true)
public class User extends Model<User> {
    @TableId
    private Long id;
    private String name;
    private Integer age;
    private String email;
    private Integer status;
    private GenderEnum gender;
    @TableField(typeHandler = FastjsonTypeHandler.class)
    private Map<String,String> contact;
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
}
  1. 编写自动填充处理器,指定填充策略
    @Component
    public class MyMetaHandler implements MetaObjectHandler {
        @Override
        public void insertFill(MetaObject metaObject) {
            setFieldValByName("createTime",new Date(),metaObject);
            setFieldValByName("updateTime",new Date(),metaObject);
        }
    
        @Override
        public void updateFill(MetaObject metaObject) {
            setFieldValByName("updateTime",new Date(),metaObject);
        }
    }
    
  2. 设置一下mysql时区,更新yml连接配置
set GLOBAL time_zone='+8:00'
select NOW();

在这里插入图片描述

防全表更新与删除插件

  • 在实际开发中,全表更新和删除是非常危险的操作,在MybatisPlus中,提供了插件和防止这种危险操作的发生。
  • 实现步骤:
  1. 注入MybatisPlusInterceptor类,并配置BlockAttackInnerInterceptor拦截器
@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
        return interceptor;
    }
}

  1. 测试全表更新,会出现抛出异常,防止了全表更新
@SpringBootTest
public class QueryTest {

    @Autowired
    private UserService userService;

	@Test
	void allUpdate(){
	    User user = new User();
	    user.setId(999L);
	    user.setName("wang");
	    user.setEmail("wang@powernode.com");
	    userService.saveOrUpdate(user,null);
	}
}

在这里插入图片描述

MybatisX快速开发插件

插件安装

  • MybatisX是一款IDEA提供的插件,目的是为了我们简化Mybatis以及MybatisPlus框架而生。
  • 在IDEA中安装插件
  1. 首先选择File -> Settings->Plugins
    在这里插入图片描述
  2. 搜索MybatisX,点击安装
    在这里插入图片描述
  3. 重启IDEA,让该插件生效,至此MybatisX插件就安装完毕

  • 插件安装好以后,我们来看一下插件的功能
  1. Mapper接口和映射文件的跳转功能
    在这里插入图片描述
    在这里插入图片描述

逆向工程

  • 逆向工程就是通过数据库表结构,逆向产生Java工程的结构,包括以下几点:
  1. 实体类
  2. Mapper接口
  3. Mapper映射文件
  4. Service接口
  5. Service实现类
  • 实现步骤:
  1. 首先使用IDEA连接mysql,填写连接信息,测试连接通过
    在这里插入图片描述
    在这里插入图片描述
  2. 找到表右键,选择插件的逆向工程选项
    在这里插入图片描述
  3. 编写逆向工程配置信息
    在这里插入图片描述
  4. 编写生成信息
    在这里插入图片描述

常见需求代码生成

  • 虽然Mapper接口中提供了一些常见方法,我们可以直接使用这些常见的方法来完成sql操作,但是对于实际场景中各种复杂的操作需求来说,依然是不够用的,所以MybatisX提供了更多的方法,以及可以根据这些方法直接生成对应的sql语句,这样使得开发变得更加的简单。
  • 可以根据名称联想常见的操作
@Mapper
public interface UserMapper extends BaseMapper<User> {
         //添加操作
        int insertSelective(User user);

        //删除操作
        int deleteByNameAndAge(@Param("name") String name, @Param("age") Integer age);

        //修改操作
        int updateNameByAge(@Param("name") String name, @Param("age") Integer age);

        //查询操作
        List<User> selectAllByAgeBetween(@Param("beginAge") Integer beginAge, @Param("endAge") Integer endAge);
}
  • 在映射配置文件中,会生成对应的sql,并不需要我们编写
<insert id="insertSelective">
    insert into powershop_user
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="id != null">id,</if>
        <if test="name != null">name,</if>
        <if test="age != null">age,</if>
        <if test="email != null">email,</if>
    </trim>
    values
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="id != null">#{id,jdbcType=BIGINT},</if>
        <if test="name != null">#{name,jdbcType=VARCHAR},</if>
        <if test="age != null">#{age,jdbcType=INTEGER},</if>
        <if test="email != null">#{email,jdbcType=VARCHAR},</if>
    </trim>
</insert>

<delete id="deleteByNameAndAge">
    delete
    from powershop_user
    where name = #{name,jdbcType=VARCHAR}
      AND age = #{age,jdbcType=NUMERIC}
</delete>

<update id="updateNameByAge">
    update powershop_user
    set name = #{name,jdbcType=VARCHAR}
    where age = #{age,jdbcType=NUMERIC}
</update>

<select id="selectAllByAgeBetween" resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List"/>
    from powershop_user
    where
    age between #{beginAge,jdbcType=INTEGER} and #{endAge,jdbcType=INTEGER}
</select>

乐观锁

问题引入

  • 并发请求就是在同一时刻有多个请求同时请求服务器资源,如果是获取信息,没什么问题,但是如果是对于信息做修改操作呢,那就会出现问题。
  • 比如目前商品的库存只剩余1件了,这个时候有多个用户都想要购买这件商品,都发起了购买商品的请求,那么能让这多个用户都购买到么,肯定是不行的,因为多个用户都买到了这件商品,那么就会出现超卖问题,库存不够是没法发货的。所以在开发中就要解决这种超卖的问题。
    在这里插入图片描述

核心问题:一个请求在执行的过程中,其他请求不能改变数据,如果是一次完整的请求,在该请求的过程中其他请求没有对于这个数据产生修改操作,那么这个请求是能够正常修改数据的。如果该请求在改变数据的过程中,已经有其他请求改变了数据,那该请求就不去改变这条数据
在这里插入图片描述

  • 想要解决这类问题,最常见的就是加锁的思想,锁可以用验证在请求的执行过程中,是否有数据发生改变。

  • 常见的数据库锁类型有两种,悲观锁和乐观锁。一次完成的修改操作是,先查询数据,然后修改数据。

    • 悲观锁:悲观锁是在查询的时候就锁定数据,在这次请求未完成之前,不会释放锁。等到这次请求完毕以后,再释放锁,释放了锁以后,其他请求才可以对于这条数据完成读写。
    • 悲观锁的优缺点:能够保证读取到的信息就是当前的信息,保证了信息的正确性,但是并发效率很低。
    • 在实际开发中使用悲观锁的场景很少,因为在并发时我们是要保证效率的。
    • 乐观锁:乐观锁是通过表字段完成设计的,核心思想是,在读取的时候不加锁,其他请求依然可以读取到这个数据,在修改的时候判断一个数据是否有被修改过,如果有被修改过,那本次请求的修改操作失效。
  • 具体的通过sql是实现

Updateset 字段 = 新值,version = version + 1 where version = 1
  • 这样做的操作是不会对于数据读取产生影响,并发的效率较高。但是可能目前看到的数据并不是真实信息数据,是被修改之前的,但是在很多场景下是可以容忍的,并不是产生很大影响。

乐观锁的使用

  1. 在数据库表中添加一个字段version,表示版本,默认值是1
    在这里插入图片描述
  2. 找到实体类,添加对应的属性,并使用@Version标注为这是一个乐观锁字段信息
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
    @Version
    private Integer version;
}

  1. 通过拦截器的配置,让每条修改的sql语句在执行的时候,都加上版本控制的功能
@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

效果测试

  • 接下来模拟一下,当出现多个修改请求的时候,是否能够做到乐观锁的效果。
  • 乐观锁的效果是,一个请求在修改的过程中,是允许另一个请求查询的,但是修改时会通过版本号是否改变来决定是否修改,如果版本号变了,证明已经有请求修改过数据了,那这次修改不生效,如果版本号没有发生变化,那就完成修改。

@Test
void updateTest2(){
    //模拟操作1的查询操作
    User user1 = userMapper.selectById(6L);

    //模拟操作2的查询操作
    User user2 = userMapper.selectById(6L);

    //模拟操作2的修改操作
    user2.setName("lisi");
    userMapper.updateById(user2);

    //模拟操作1的修改操作
    user1.setName("zhangsan");
    userMapper.updateById(user1);
}

  • 代码的执行过程
    1. 操作1的查询:此时版本为2
      在这里插入图片描述
    2. 操作2的查询:此时版本为2
      在这里插入图片描述
    3. 操作2的修改:此时检查版本,版本没有变化,所以完成修改,并将版本改为3
      在这里插入图片描述
    4. 操作1的修改:此时检查版本,版本已经有最初获取的版本信息发生了变化,所以杜绝修改
      在这里插入图片描述

代码生成器

  • 代码生成器和逆向工程的区别在于,代码生成器可以生成更多的结构,更多的内容,允许我们能够配置生成的选项更多。
  1. 引入依赖
    <!--代码生成器依赖-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-generator</artifactId>
        <version>3.5.3</version>
    </dependency>
    
    <!--freemarker模板依赖-->
    <dependency>
        <groupId>org.freemarker</groupId>
        <artifactId>freemarker</artifactId>
        <version>2.3.31</version>
    </dependency>
    
  2. 编写代码生成器代码
    • 示例一:
    public class CodeGenerator {
    	/*
    	SELECT table_name
          FROM information_schema.tables
          WHERE table_schema = 'xxx'
          ORDER BY table_name DESC;
    	*/
    
        public static String scanner(String tip) {
            Scanner scanner = new Scanner(System.in);
            StringBuilder help = new StringBuilder();
            help.append("请输入" + tip + ":");
            System.out.println(help.toString());
            if (scanner.hasNext()) {
                String ipt = scanner.next();
                if (!ipt.equals("")) {
                    return ipt;
                }
            }
            throw new MybatisPlusException("请输入正确的" + tip + "!");
        }
    
        public static void main(String[] args) {
            // 代码生成器
            AutoGenerator mpg = new AutoGenerator();
    
            // 全局配置
            GlobalConfig gc = new GlobalConfig();
            String projectPath = System.getProperty("user.dir");
            gc.setOutputDir(projectPath + "/src/main/java");//设置代码生成路径
            gc.setFileOverride(true);//是否覆盖以前文件
            gc.setOpen(false);//是否打开生成目录
            gc.setAuthor("xxx");//设置项目作者名称
            gc.setIdType(IdType.AUTO);//设置主键策略
            gc.setBaseResultMap(true);//生成基本ResultMap
            gc.setBaseColumnList(true);//生成基本ColumnList
            gc.setServiceName("%sService");//去掉服务默认前缀
            gc.setDateType(DateType.ONLY_DATE);//设置时间类型
            mpg.setGlobalConfig(gc);
    
            // 数据源配置
            DataSourceConfig dsc = new DataSourceConfig();
            dsc.setUrl("jdbc:mysql://localhost:3306/care_home?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8");
            dsc.setDriverName("com.mysql.cj.jdbc.Driver");
            dsc.setUsername("root");
            dsc.setPassword("xxx");
            mpg.setDataSource(dsc);
    
            // 包配置
            PackageConfig pc = new PackageConfig();
            pc.setParent("com.test");
            pc.setMapper("com/test/mapper");
            pc.setXml("mapper.xml");
            pc.setEntity("com/test/pojo");
            pc.setService("com/test/service");
            pc.setServiceImpl("service.impl");
            pc.setController("com/test/controller");
            mpg.setPackageInfo(pc);
    
            // 策略配置
            StrategyConfig sc = new StrategyConfig();
            sc.setNaming(NamingStrategy.underline_to_camel);
            sc.setColumnNaming(NamingStrategy.underline_to_camel);
            sc.setEntityLombokModel(true); //自动lombok
            sc.setRestControllerStyle(true);
            sc.setControllerMappingHyphenStyle(true);
    
            sc.setLogicDeleteFieldName("deleted");//设置逻辑删除
    
            //设置自动填充配置
            TableFill gmt_create = new TableFill("create_time", FieldFill.INSERT);
            TableFill gmt_modified = new TableFill("update_time", FieldFill.INSERT_UPDATE);
            ArrayList<TableFill> tableFills=new ArrayList<>();
            tableFills.add(gmt_create);
            tableFills.add(gmt_modified);
            sc.setTableFillList(tableFills);
    
            //乐观锁
            sc.setVersionFieldName("version");
            sc.setRestControllerStyle(true);//驼峰命名
    
            //  sc.setTablePrefix("tbl_"); 设置表名前缀
            sc.setInclude(scanner("表名,多个英文逗号分割").split(","));
            mpg.setStrategy(sc);
    
            // 生成代码
            mpg.execute();
        }
    
    }
    
    • 示例二:
/**
 * @author 缘友一世
 * date 2023/7/19-16:09
 */
public class CodeGenerator {
    public static void main(String[] args) {
        String projectPath = System.getProperty("user.dir"); // 获取当前项目的绝对路径
        String MainPath=projectPath+"/src/main/java";
        String url="jdbc:mysql://localhost:3306/db2?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false";
        String username="root";
        String password="xxx";
        String author="yang";
        String moduleName="system";
        String mapperLocation=projectPath+"/src/main/resources/mapper/"+moduleName;
        String parentPackageName="com.yang";
        /*
        CREATE TABLE x_user (
            id int(11) NOT NULL AUTO_INCREMENT,
            username varchar(50) NOT NULL ,
            password varchar(100) DEFAULT NULL,
            email varchar(50) DEFAULT NULL,
            phone varchar(20) DEFAULT NULL,
            status int(1) DEFAULT NULL,
            avatar varchar(200) DEFAULT NULL,
            deleted INT(1) DEFAULT 0,
            PRIMARY KEY (id)
        )ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

        DELETE FROM x_user
        WHERE id > 20;
         */
        /*
        * SELECT table_name
          FROM information_schema.tables
          WHERE table_schema = 'xxx'
          ORDER BY table_name DESC; */
        String tables="x_user";
        FastAutoGenerator.create(url, username, password)
                .globalConfig(builder -> {
                    builder.author(author) // 设置作者
                            //.enableSwagger() // 开启 swagger 模式
                            //.fileOverride() // 覆盖已生成文件
                            .outputDir(MainPath); // 指定输出目录
                })
                .dataSourceConfig(builder -> builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {
                    int typeCode = metaInfo.getJdbcType().TYPE_CODE;
                    if (typeCode == Types.SMALLINT) {
                        // 自定义类型转换
                        return DbColumnType.INTEGER;
                    }
                    return typeRegistry.getColumnType(metaInfo);

                }))
                .packageConfig(builder -> {
                    builder.parent(parentPackageName) // 设置父包名
                            .moduleName(moduleName) // 设置父包模块名
                            .pathInfo(Collections.singletonMap(OutputFile.xml, mapperLocation)); // 设置mapperXml生成路径
                })
                .strategyConfig(builder -> {
                    builder.addInclude(tables) // 设置需要生成的表名
                            .addTablePrefix("x_"); // 设置过滤表前缀
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();
    }

}

执行SQL分析打印

  • 可以使用MybatisPlus提供的SQL分析打印的功能,来获取SQL语句执行的时间。
  1. 由于该功能依赖于p6spy组件,所以需要在pom.xml中先引入该组件
    <dependency>
        <groupId>p6spy</groupId>
        <artifactId>p6spy</artifactId>
        <version>3.9.1</version>
    </dependency>
    
  2. 在application.yml中进行配置
spring:
  datasource:
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    url: jdbc:p6spy:mysql
  1. 在resources下,创建 spy.properties配置文件
#3.2.1以上使用modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory

# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger

#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger

# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger

# 设置 p6spy driver 代理
deregisterdrivers=true

# 取消JDBC URL前缀
useprefix=true

# 配置记录 Log 例外,可去掉的结果集error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset

# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss

# 实际驱动可多个
#driverlist=org.h2.Driver

# 是否开启慢SQL记录
outagedetection=true

# 慢SQL记录标准 2 秒
outagedetectioninterval=2

  1. 测试:执行查询所有的操作,可以看到sql语句的执行时间
    在这里插入图片描述

多数据源

  • 分库分表:当一个项目的数据库的数据十分庞大时,在完成SQL操作的时候,需要检索的数据就会更多,我们会遇到性能问题,会出现SQL执行效率低的问题。
  • 针对这个问题,我们的解决方案是,将一个数据库中的数据,拆分到多个数据库中,从而减少单个数据库的数据量,从分摊访问请求的压力和减少单个数据库数据量这两个方面,都提升了效率。

  • 在MybatisPlus中,如何演示数据源切换的效果
  1. 先创建一个新的模块,将之前模块中的内容复制过来
    在这里插入图片描述
  2. 引入依赖
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.1.0</version>
</dependency>
  1. 创建新的数据库,提供多数据源环境
    在这里插入图片描述
    在这里插入图片描述
  2. 编写配置文件,指定多数据源信息
spring:
  datasource:
    dynamic:
      primary: master
      strict: false
      datasource:
        master:
          username: root
          password: xxx
          url: jdbc:mysql://localhost:3306/mybatisplus?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
          driver-class-name: com.mysql.cj.jdbc.Driver
        slave_1:
          username: root
          password: xxx
          url: jdbc:mysql://localhost:3306/mybatisplus2?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
          driver-class-name: com.mysql.cj.jdbc.Driver


  1. 创建多个Service,分别使用@DS注解描述不同的数据源信息
@Service
@DS("master")
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
}
@Service
@DS("slave_1")
public class UserServiceImpl2 extends ServiceImpl<UserMapper, User> implements UserService{
}
  1. 测试service多数据源环境执行结果
@SpringBootTest
class Mp03ApplicationTests {

    @Autowired
    private UserServiceImpl userServiceImpl;

    @Autowired
    private UserServiceImpl2 userServiceImpl2;

    @Test
    public void select(){
        User user = userServiceImpl.getById(1L);
        System.out.println(user);
    }

    @Test
    public void select2(){
        User user = userServiceImpl2.getById(1L);
        System.out.println(user);
    }
}

  1. 观察测试结果,发现结果可以从两个数据源中获取
    在这里插入图片描述
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/809304.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

本地仓库推送至远程仓库

1. 本地生成ssh密钥对 ssh-keygen -t rsa -C 邮箱2. 添加公钥到gitlab/github/gitee上 打开C:\Users\用户名\.ssh目录下生成的密钥文件id_rsa.pub&#xff0c;把内容复制到如下文本框中 删除Expiration date显示的日期&#xff0c;公钥有效期变成永久&#xff0c;之后点Add K…

LeetCode 刷题 数据结构 数组 485 最大连续1的个数

给定一个二进制数组 nums &#xff0c; 计算其中最大连续 1 的个数。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,0,1,1,1] 输出&#xff1a;3 解释&#xff1a;开头的两位和最后的三位都是连续 1 &#xff0c;所以最大连续 1 的个数是 3.示例 2: 输入&#xff1a;nums […

ROS中使用RealSense-D435

文章目录 D435简介RealSense的SDK2.0安装方法1&#xff1a;直接利用安装源安装注册服务器公匙将服务器添加到存储库列表安装库 方法2&#xff1a;利用源码安装GitHub下载librealsense安装编译依赖运行脚本cmake编译 软件显示 ROS接口安装启动节点查看话题rviz显示点云 Python接…

MySQL中IN的取值范围较大时会导致索引失效

一&#xff1a;分析MySQL In查询为什么所有不生效 结论&#xff1a;IN肯定会走索引&#xff0c;但是当IN的取值范围较大时会导致索引失效&#xff0c;走全表扫描 navicat可视化工具使用explain函数查看sql执行信息 1.1 场景1&#xff1a;当IN中的取值只有一个主键时 我们只需要…

怎么在线制作证件?教你一键生成证件照

无论是申请身份证、护照、驾照还是学生证&#xff0c;都需要一张清晰、规范的证件照。但是&#xff0c;为了拍摄一张完美的证件照&#xff0c;需要付出不少时间和精力。而现在&#xff0c;我们可以使用压缩图网站提供的证件照制作工具&#xff0c;轻松制作出一张清晰、规范的证…

力扣刷题记录---利用python实现链表的基本操作

文章目录 前言一、利用python实现链表的基本操作1.节点的定义使用类实现&#xff1a;1.链表的定义使用类实现&#xff1a;3.判断是否为空函数实现&#xff1a;4.链表长度函数实现&#xff1a;5.遍历链表函数实现&#xff1a;6.头插法函数实现&#xff1a;7.尾插法函数实现&…

手把手一起上传本地项目至Gitee仓库

1、Gitee新建仓库 创建自己的Gitee账号&#xff0c;新建仓库&#xff0c;如图所示&#xff1a; 根据自己的项目情况&#xff0c;填写仓库信息&#xff0c;如图所示&#xff1a; 仓库创建完成&#xff0c;如图所示&#xff1a; 2、下载Git 下载地址可用链接: https://registry…

xxl-Job分布式任务调度

1.概述 1.1 什么是任务调度 我们可以先思考一下业务场景的解决方案&#xff1a; 某电商系统需要在每天上午10点&#xff0c;下午3点&#xff0c;晚上8点发放一批优惠券。某银行系统需要在信用卡到期还款日的前三天进行短信提醒。某财务系统需要在每天凌晨0:10结算前一天的财…

流数据湖平台Apache Paimon(一)概述

文章目录 第1章 概述1.1 简介1.2 核心特性1.3 基本概念1.3.1 Snapshot1.3.2 Partition1.3.3 Bucket1.3.4 Consistency Guarantees一致性保证 1.4 文件布局1.4.1 Snapshot Files1.4.2 Manifest Files1.4.3 Data Files1.4.4 LSM Trees 第1章 概述 1.1 简介 Flink 社区希望能够将…

新材料技术的优势

目录 1.什么是新材料技术 2.新材料技术给人类带来了哪些便利 3.新材料技术未来的发展趋势 1.什么是新材料技术 新材料技术指的是通过科学和工程技术的手段开发和应用全新的材料&#xff0c;以满足特定的需求和应用。新材料技术是材料科学和工程领域的重要研究方向&#xff0…

【Java】使用JDBC操作MySQL 8

文章目录 1. JDBC概述2. JDBC快速入门2.1 下载驱动jar包2.2 数据准备2.3 创建工程2.4 编写代码 3. JDBC API详解3.1 DriverManager3.2 Connection3.2.1 获取执行SQL对象3.2.1 管理事务 3.3 Statement3.3.1 执行DML语句3.3.2 执行DDL语句 3.4 ResultSet3.4.1 ResultSet对象方法3…

解读 Zebec Protocol 发布的最新路线图,向 Web2 世界跨越的野望

近期&#xff0c;流支付协议 Zebec Protocol 发布了最新的路线图&#xff0c;揭示了生态在未来一年的全新发展规划。目前&#xff0c; Zebec Protocol 生态打造了一套全新的产品矩阵&#xff0c;包括模块化 Layer3 链 Nautilus Chain 、流支付应用 Zebec APP 以及薪酬管理协议 …

小红书怎么推广 方法经验总结

大家好&#xff0c;我是网媒智星&#xff0c;今天跟大家分享一下小红书怎么推广&#xff0c;总结一些方法经验。 小红书上的引流变现逻辑包括以下步骤&#xff1a; 选题—封面标题—内容评论—钩子—私域—成交—裂变 如果你希望在小红书上进行正确、安全的引流&#xff0c;…

Virtualbox虚拟机中Ubuntu忘记密码

1、首先重新启动Ubuntu系统&#xff0c;鼠标快速点一下Virtualbox虚拟机窗口获取焦点&#xff0c;然后按住shift键&#xff0c;以调出grub启动菜单。 2、根据提示按下键盘E键进入编辑模式&#xff0c;向下移动光标&#xff0c;将如下"ro quiet splash $vt_handoff"部…

Java开发环境以及项目搭建案例汇总

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 友情提示 1、 假若你的设备已有可用的Java开发基础环境&#xff0c;则无需重新搭建 2、 假若你需重新搭建Java开发&#xff0c;请务必彻底卸载之前的环境 3、 请尽量保证与…

Reinforcement Learning with Code 【Code 1. Tabular Q-learning】

Reinforcement Learning with Code 【Code 1. Tabular Q-learning】 This note records how the author begin to learn RL. Both theoretical understanding and code practice are presented. Many material are referenced such as ZhaoShiyu’s Mathematical Foundation o…

WIZnet W5100S-EVB-Pico 静态IP配置教程(二)

W5100S是一个硬连线TCP/IP封装以太网控制器W5100S支持间接并行总线和高速SPI接口2种方式与主机进行通信。其内部还集成了以太网数据链路层&#xff08;MAC&#xff09;和10Base -T/100Base -T 以太网物理层&#xff08;PHY&#xff09;&#xff0c;支持自动协商&#xff08;10/…

记录vue的一些踩坑日记

记录vue的一些踩坑日记 安装Jq npm install jquery --save vue列表跳转到详情页&#xff0c;再返回列表的时候不刷新页面并且保持原位置不变&#xff1b; 解决&#xff1a;使用keepAlive 在需要被缓存的页面的路由中添加&#xff1a;keepAlive: true, {path: /viewExamine,nam…

Docker安装 Mysql 8.x 版本

文章目录 Docker安装 Mysql 8.0.22Mysql 创建账号并授权Mysql 数据迁移同版本数据迁移跨版本数据迁移 Mysql 5.x 版本与 Mysql 8.x版本是两个大版本&#xff0c;这里演示安装Mysql 8.x版本 Docker安装 Mysql 8.0.22 # 下载mysql $ docker pull mysql 默认安装最新…

SolidWorks(1)

打开solidworks,选择零件选择草图、绘制草图选择上视基准面 最后完成草图 选择拉伸切除 最终成品 鼠标按住中键&#xff0c;进行旋转