MybatisPlus拓展篇

文章目录

  • 逻辑删除
  • 通用枚举
  • 字段类型处理器
  • 自动填充功能
  • 防全表更新与删除插件
  • 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. 观察测试结果,发现结果可以从两个数据源中获取
    在这里插入图片描述
    在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

uni-app点击按钮弹出提示框(以弹窗的形式显示),选择确定和取消

学习目标&#xff1a; 学习目标如下所示&#xff1a; uni-app点击提交按钮后弹出提示框&#xff0c;&#xff08;以弹窗的形式显示&#xff09;,提示用户是否确认提交&#xff08;即确定和取消&#xff09;&#xff0c;点击确定后调用真正的提交方法&#xff0c;将数据传给后端…

【基于矢量射线的衍射积分 (VRBDI)】基于矢量射线的衍射积分 (VRBDI) 和仿真工具(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

文心一言大数据模型-文心千帆大模型平台

官网&#xff1a; 文心千帆大模型平台 (baidu.com) 文心千帆大模型 (baidu.com) 模型优势 1、模型效果优&#xff1a;所需标注数据少&#xff0c;在各场景上的效果处于业界领先水平 2、生成能力强&#xff1a;拥有丰富的AI内容生成&#xff08;AIGC&#xff09;能力 3、应用…

Flink CEP(二) 运行源码解析

通过DemoApp学习一下&#xff0c;CEP的源码执行逻辑。为下一篇实现CEP动态Pattern奠定理论基础。 1. Pattern的定义 Pattern<Tuple3<String, Long, String>,?> pattern Pattern.<Tuple3<String, Long, String>>begin("begin").where(new…

详解python中的垃圾回收机制

目录 什么是垃圾回收机制 垃圾回收的工作流程 为什么要进行垃圾回收 详解python中的垃圾回收机制 总结 什么是垃圾回收机制 垃圾回收&#xff08;Garbage Collection&#xff09;是一种自动内存管理机制&#xff0c;用于检测和释放不再被程序使用的内存资源&#xff0c;以…

基于开源IM即时通讯框架MobileIMSDK:RainbowChat v9.0版已发布

关于MobileIMSDK MobileIMSDK 是一套专门为移动端开发的开源IM即时通讯框架&#xff0c;超轻量级、高度提炼&#xff0c;一套API优雅支持UDP 、TCP 、WebSocket 三种协议&#xff0c;支持iOS、Android、H5、标准Java平台&#xff0c;服务端基于Netty编写。 工程开源地址是&am…

Linux 终端生成二维码

1、安装qrencode [rootnode1 script]# yum -y install qrencode2、输出正常的 [rootnode1 ~]# echo https://www.github.com|qrencode -o - -t utf83、输出彩色的 [rootnode1 ~]# qrencode -t utf8 -s 1 https://www.github.com|lolcatPS&#xff1a;没有lolcat命令 #由于…

【计算机视觉中的 GAN 】 - 生成学习简介(1)

一、说明 在阅读本文之前&#xff0c;强烈建议先阅读预备知识&#xff0c;否则缺乏必要的推理基础。本文是相同理论GAN原理的具体化范例&#xff0c;阅读后有两个好处&#xff1a;1 巩固了已经建立的GAN基本概念 2 对具体应用的过程和套路进行常识学习&#xff0c;这种练习题一…

数据结构——单链表

不能毁灭我的&#xff0c;终将使我更强大 文章目录 一、链表 二、单链表 三、实现单链表 1.定义节点 2.由数据生成节点 3.连接并打印链表 4.单链表的基本接口 头插 头删 尾插 尾删 由数据Data找节点 在pos之前插入节点 在pos之后插入节点 删除pos节点 删除po…

Matplotlib_绘制柱状图

绘制柱状图 &#x1f9e9;bar方法 bar()是Matplotlib.pyplot库中用于绘制条形图&#xff08;bar chart&#xff09;的函数。条形图是一种常见的数据可视化图表&#xff0c;用于显示不同类别之间的比较。 函数签名&#xff1a; matplotlib.pyplot.bar(x, height, width0.8, …

【KO】vite使用 git bash here创建vue3项目时方向键失败!

文章目录 起因过程结果 起因 今天使用vite创建ue3项目&#xff0c;因为git使用习惯了就直接用git运行创建命令&#xff0c;前两步都没啥问题&#xff0c;到选择框架的时候问题来了&#xff0c;方向键无效。如图&#xff1a; 过程 常理来说是直接用方向键↑和↓进行选择&…

3d激光slam建图与定位(1)_基于ndt算法定位

一.代码实现流程 二.ndt算法原理 一.该算法定位有三个进程文件 1.map_loader.cpp用于点云地图的读取&#xff0c;从文件中读取点云后对这个点云地图进行旋转平移后发布点云地图到ros #include "map_loader.h"MapLoader::MapLoader(ros::NodeHandle &nh){std::st…

【深度学习笔记】动量梯度下降法

本专栏是网易云课堂人工智能课程《神经网络与深度学习》的学习笔记&#xff0c;视频由网易云课堂与 deeplearning.ai 联合出品&#xff0c;主讲人是吴恩达 Andrew Ng 教授。感兴趣的网友可以观看网易云课堂的视频进行深入学习&#xff0c;视频的链接如下&#xff1a; 神经网络和…

Python开发之手动实现一维线性插值

Python开发之手动实现一维线性插值 1.线性插值法介绍2.手动实现线性插值3.案例一&#xff1a;手动实现线性插值4.使用pandas的插值方法实现要求(推荐)5.案例二&#xff1a;对一组数据进行线性插值和SG滤波处理 前言&#xff1a;主要介绍手动实现一维线性插值以及pandas里面的in…

【Docker】容器的数据卷

目录 一、数据卷的概念与作用 二、数据卷的配置 三、数据卷容器的配置 一、数据卷的概念与作用 在了解什么是数据卷之前我们先来思考以下这些问题&#xff1a; 1.如果我们一个容器在使用后被删除&#xff0c;那么他里面的数据是否也会丢失呢&#xff1f;比如容器内的MySQL的…

2023年的深度学习入门指南(21) - 百川大模型

2023年的深度学习入门指南(21) - 百川大模型 前面我们用了三节的篇幅介绍了目前最强大的开源模型LLaMA2。这一节我们说一说国产大模型的一个代表&#xff0c;百川大模型。 使用百川大模型 第一步我们先把百川用起来&#xff0c;然后再研究如何训练和其原理如何。 百川的使用…

Mybatis使用collection映射一对多查询分页问题

场景&#xff1a;页面展示列表&#xff0c;需要查询多的字段&#xff0c;和一的字段。并且还要分页。 这时候直接想到的是手写sql。 /*** 标签*/private List<BasicResidentTags> tags;Data TableName("basic_resident_tags") public class BasicResidentTag…

vue3 - element-plus 上传各种 word pdf 文件、图片视频并上传到服务器功能效果,示例代码开箱即用。

效果图 在 vue3 项目中,使用 element plus 组件库的 el-upload 上传组件,进行文件、图片图像的上传功能示例。 完整代码 可直接复制,再改个接口地址。 在这里上传图片和文件是分成

新产品:Stimulsoft Forms 2023.3.1 Crack

Stimulsoft Forms 是一个用于交互式收集和处理用户数据的组件。表单工具可以轻松集成到您的项目或应用程序中&#xff0c;具有直观且用户友好的界面&#xff0c;并允许您创建丰富的表单模板。Stimulsoft Forms 是应用程序中与用户交互的新水平 什么是 Stimulsoft Forms&#xf…

华为数通HCIP-EVPN基础

MP-BGP MP-BGP&#xff08;Multiprotocol Extensions for BGP-4&#xff09;在RFC4760中被定义&#xff0c;用于实现BGP-4的扩展以允许BGP携带多种网络层协议&#xff08;例如IPv6、L3VPN、EVPN等&#xff09;。这种扩展有很好的后向兼容性&#xff0c;即一个支持MP-BGP的路由…