(3)MyBatis-Plus待开发
常用注解
@TableName
MyBatis-Plus在确定操作的表时,由BaseMapper的泛型决定即实体类型决定,且默认操作的表名和实体类型的类名一致,如果不一致则会因找不到表报异常
//向表中插入一条数据
@Test
public void testInsert(){User user = new User(null, "张三", 23, "zhangsan@atguigu.com");//INSERT INTO user (id, name, age, email) VALUES ( ?, ?, ?, ? )int result = userMapper.insert(user);System.out.println("受影响行数:"+result);//获取插入数据的主键id为1475754982694199298//MyBatis-Plus在实现插入数据时,如果我们没有指定id,他默认基于雪花算法的策略生成一个id插入到表中System.out.println("id自动获取:"+user.getId());
}
在实体类类型上添加@TableName(“t_user”)用来标识实体类对应的表
@Data
@TableName("t_user")
public class User{private Long id;private String name;private Integer age;private String email;public User() {}
}
全局配置: 实际开发中实体类所对应的表都有固定的前缀(例如t_ 或tbl_),可以使用MyBatis-Plus提供的全局配置, 为所有实体类所对应的表名设置默认的前缀
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl# 设置MyBatis-Plus的全局配置global-config:db-config:# 设置实体类所对应的表的统一前缀table-prefix: t_
@TableId的value属性和type属性
MyBatis-Plus在实现CRUD时, 会默认将实体类的id属性作为主键列, 并在插入数据时默认基于雪花算法的策略生成一个id擦入到数据库
测试将实体类中的属性id改为uid(此时uid对于MyBatis-Plus来说就是一个普通字段),将表中的字段id也改为uid
//向表中插入一条数据
@Test
public void testInsert(){User user = new User(null, "张三", 23, "zhangsan@atguigu.com");//INSERT INTO user (name, age, email) VALUES ( ?, ?, ?)//uid对于MyBatis-Plus来说就是一个普通字段,如果我们没有指定值默认就向数据表插入的就为null,不再是基于雪花算法生成的值int result = userMapper.insert(user);System.out.println("受影响行数:"+result);System.out.println("id自动获取:"+user.getId());
}
在实体类的属性上添加@TableId指定value属性表示将该属性对应的字段作为主键列(默认以属性的属性名对应的字段名为主键字段)
- @TableId的value属性: 用来指定该属性对应的字段名作为主键
@Data
public class User {//@TableId(value="uid")@TableId("uid")private Long id;private String name;private Integer age;private String email;
}//向表中插入一条数据
@Test
public void testInsert(){User user = new User(null, "张三", 23, "zhangsan@atguigu.com");//INSERT INTO user (uid,name, age, email) VALUES ( ?, ?, ?,?)int result = userMapper.insert(user);System.out.println("受影响行数:"+result);System.out.println("id自动获取:"+user.getId());
}
@TableId的type属性用来定义主键的生成策略, IdType表示主键类型
- 如果设置了主键的值,就会根据设置的值插入数据库, 不再基于任何主键策略
值 | 描述 |
---|---|
IdType.ASSIGN_ID(默认) | 基于雪花算法的策略生成数据id,与数据库id是否设置自增无关 |
IdType.AUTO | 使用数据库的自增策略,注意,该类型请确保数据库设置了id自增, |
在实体类的属性上添加@TableId指定type属性设置主键的生成策略
@Data
public class User {@TableId(value="uid",type = IdType.AUTO)@TableId("uid")private Long id;private String name;private Integer age;private String email;
}
//向表中插入一条数据
@Test
public void testInsert(){User user = new User(null, "张三", 23, "zhangsan@atguigu.com");//INSERT INTO user (name, age, email) VALUES ( ?, ?, ?,?)//采用主键自动递增策略后id就不用再通过雪花算法生成一个值赋给id属性然后插入数据库中,主键自动递增默认就会自动递增不用赋值int result = userMapper.insert(user);System.out.println("受影响行数:"+result);System.out.println("id自动获取:"+user.getId());
}
全局配置: 可以使用MyBatis-Plus提供的全局配置, 设置统一的主键生成策略
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl# 设置MyBatis-Plus的全局配置global-config:db-config:# 设置实体类所对应的表的统一前缀table-prefix: t_# 设置统一的主键生成策略id-type: auto
雪花算法
随着数据规模的增长雪花算法的作用就是应对逐渐增长的访问压力和数据量
- 数据库性能的扩展方式主要包括:业务分库、主从复制(主服务器实现写的功能,从服务器实现写的功能, 保存证主从服务器的数据一致性),数据库分表
数据库分表是将不同业务数据分散存储到不同的数据库服务器,能够支撑百万甚至千万用户规模的业务
- 如果业务继续发展,同一业务的单表数据也会达到单台数据库服务器的处理瓶颈, 此时就需要对单表数据进行拆分
单表数据拆分有两种方式:垂直分表和水平分表
-
垂直分表适合将表中某些不常用且占了大量空间的列拆分出去,用户在筛选其他用户的时候,主要是用 age 和 sex 两个字段进行查询,而 nickname 和 description 两个字段本身比较长主要用于展示,一般不会在业务查询中用到。因此我们可以将这两个字段独立到另外一张表中,这样在查询 age 和 sex 时,就能带来一定的性能提升
-
水平分表适合表行数特别大的表,根据表的访问性能 , 简单的表行数超过 5000 万就必须进行分表, 比较复杂的表, 可能超过 1000 万就要分表了 , 水平分表相比垂直分表,会引入更多的复杂性,例如要求全局唯一的数据id该如何处理
-
水平分表相比垂直分表,会引入更多的复杂性,例如要求全局唯一的数据id该如何处理
主键递增: 可以按照 1000000 的范围大小进行分段,1 ~ 999999 放到表 1中,1000000 ~ 1999999 放到表 2 中
- 复杂点分段大小的选取不好确定:分段太小会导致切分后子表数量过多,增加维护复杂度;分段太大可能会导致单表依然存在性能问题,一般建议分段大小在 100 万至 2000 万之间,具体需要根据业务选取合适的分段大小
- 优点:可以随着数据的增加平滑地扩充新的表。例如现在的用户是 100 万,如果增加到 1000 万,只需要增加新的表就可以了,原有的数据不需要动
- 缺点:分布不均匀, 假如按照 1000 万来进行分表,有可能某个分段实际存储的数据量只有 1 条,而另外一个分段实际存储的数据量有 1000 万条
取模: 如果有10 个数据库表可以用 user_id % 10 的值来表示数据所属的数据库表编号,ID 为 985 的用户放到编号为 5 的子表中
- 复杂点初始表数量的大小不好确定: 表数量太多维护比较麻烦,表数量太少又可能导致单表性能存在问题
- 优点:表分布比较均匀
- 缺点:扩充新的表很麻烦,所有数据都要重分布
雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性
- 优点:整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞,并且效率较高
@TableField
在实体类属性上使用@TableField, 表示当前属性对应的字段为一个普通字段(默认属性名就是表中的字段名)
- @TableField的value属性可以设置属性所对应的字段名
- 若实体类中的属性使用的是驼峰命名风格,而表中的字段使用的是下划线命名风格, 此时MyBatis-Plus会自动将下划线命名风格转化为驼峰命名风格
@Data
public class User {private Long id;//userName对应的SQL语句INSERT INTO user (id, user_name, age, email ) VALUES ( ?, ?, ?, ? )//name对应的SQL语句INSERT INTO user (id, name, age, email ) VALUES ( ?, ?, ?, ? )@TableField("user_name")private String userName;private Integer age;private String email;
}
//向表中插入一条数据
@Test
public void testInsert(){User user = new User(null, "张三", 23, "zhangsan@atguigu.com");//int result = userMapper.insert(user);System.out.println("受影响行数:"+result);System.out.println("id自动获取:"+user.getId());
}
@TableLogic
数据库表中的删除分为两种
- 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
- 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录(可以进行数据恢复)
第一步: 数据库中创建逻辑删除状态列,设置默认值为0(表示该记录处于未删除状态,1表示已删除状态)
第二步: 实体类中添加逻辑删除属性
@Data
public class User {@TableId("uid")private Long id;@TableFiled("user_name")private String name;private Integer age;private String email;@TableLogicprivate Integer isDeleted;
}
测试逻辑删除
//通过多个id批量删除
@Test
public void testDeleteBatchIds(){List<Long> idList = Arrays.asList(1L, 2L, 3L);//物理删除执行的SQL: DELETE FROM user WHERE uid IN ( ? , ? , ? )//逻辑删除真正执行的是修改: UPDATE t_user SET is_deleted=1 WHERE uid=? AND is_deleted=0int result = userMapper.deleteBatchIds(idList);System.out.println("受影响行数:"+result);
}//查询所有数据,返回一个list集合
@Test
public void testSelectList(){//直接查询:SELECT uid As id,user_name As name,age,email FROM user//查询数据被逻辑删除的数据默认不会被查询:SELECT uid As id,user_name As name,age,email ,is_deleted FROM t_user WHERE is_deleted=0List<User> list = userMapper.selectList(null);list.forEach(System.out::println);
}
条件构造器和常用接口
wapper介绍
条件构造器的两个条件之间默认就是AND并列关系,如果需要或者的关系则需要调用构造器的or()方法
条件构造器类型 | 作用 |
---|---|
Wrapper | 条件构造抽象类,最顶端父类 |
AbstractWrapper | 用于查询条件封装,生成 sql 的 where 条件 |
QueryWrapper | 查询/删除条件封装 |
UpdateWrapper | Update条件封装 |
AbstractLambdaWrapper | 使用Lambda语法 |
LambdaQueryWrapper | 用于Lambda语法使用的查询Wrapper |
LambdaUpdateWrapper | Lambda更新封装Wrapper |
@Data
public class User {private Long id;@TableFiled("user_name")private String name;private Integer age;private String email;@TableLogicprivate Integer isDeleted;
}
QueryWrapper
使用queryWrapper实现查询功能
@springBootTest
public class MybatisPlusWrapperTest{@Autowiredprivate UserMapper userMapper;//组装查询条件@Testpublic void test01(){//查询用户名包含a,年龄在20到30之间,并且邮箱不为null的用户信息,使用了逻辑删除查询的都是未删除的数据/*SELECT id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (username LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)*/QueryWrapper<User> queryWrapper = new QueryWrapper<>();//链式结构的调用,这些方法的返回值最后都是queryWrapper对象queryWrapper.like("username", "a").between("age", 20, 30).isNotNull("email");//将查询的结果集映射到实体类的属性然乎放入到List集合当中List<User> list = userMapper.selectList(queryWrapper);list.forEach(System.out::println);}//组装排序条件@Testpublic void test02(){//按年龄降序查询用户,如果年龄相同则按id升序排列//SELECT id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 ORDER BY age DESC,id ASCQueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.orderByDesc("age").orderByAsc("id");List<User> users = userMapper.selectList(queryWrapper);users.forEach(System.out::println);}
}
查询特定的字段和组装子查询
@springBootTest
public class MybatisPlusWrapperTest{@Autowiredprivate UserMapper userMapper;//查询用户信息的user_name和age字段,selectList默认是查询所有的字段@Testpublic void test05() {//SELECT user_name,age FROM t_user WHERE is_deleted=0QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.select("user_name", "age");//selectMaps()查询结果是一个Map集合 被放入到了List集合,通常配合select()使用,避免User对象中没有被查询到的列值为nullList<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);maps.forEach(System.out::println);}//组装子查询,查询id小于等于3的用户信息@Testpublic void test06() {//SELECT id,use_rname,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND id IN(select id from t_user where id <= 3)QueryWrapper<User> queryWrapper = new QueryWrapper<>();//第一个参数是子查询要判断的字段,第二个参数是子查询要判断的数据queryWrapper.inSql("id", "select id from t_user where id <= 3");List<User> list = userMapper.selectList(queryWrapper);list.forEach(System.out::println);}
}
条件构造器的优先级默认按照从左到右的顺序执行, 调用构造器的and()方法相当于在sql语句加了一个括号,使用queryWrapper实现修改和删除功能
@springBootTest
public class MybatisPlusWrapperTest{@Autowiredprivate UserMapper userMapper;//组装逻辑删除条件@Testpublic void test03(){//将(年龄大于20并且用户名中包含有a)或邮箱为null的用户信息修改,条件构造器默认就是并列条件@Testpublic void test04() {QueryWrapper<User> queryWrapper = new QueryWrapper<>();//UPDATE t_user SET age= ?, email= ? WHERE is_deleted=0 AND (user_name LIKE ? AND age > ? OR email IS NULL)queryWrapper.like("user_name", "a").gt("age", 20).or().isNull("email");//设置实体类中要修改的字段,没设置的字段不会被修改User user = new User();user.setAge(18);user.setEmail("user@atguigu.com");int result = userMapper.update(user, queryWrapper);System.out.println("受影响的行数:" + result);}//构造器的优先级,将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改@Testpublic void test04() {QueryWrapper<User> queryWrapper = new QueryWrapper<>();//UPDATE t_user SET age=?, email=? WHERE is_deleted=0 AND (user_name LIKE ? AND (age > ? OR email IS NULL))//queryWrapper.like("user_name", "a").and((QueryWrapper i){return i.gt("age", 20).or().isNull("email")});//lambda表达式内的逻辑优先运算,i表示条件构造器,and方法的参数是Consumer<QueryWrapper<实体类>>接口的实现类//queryWrapper.like("user_name", "a").and((QueryWrapper i){return i.gt("age", 20).or().isNull("email")});queryWrapper.like("user_name", "a").and(i -> i.gt("age", 20).or().isNull("email"));User user = new User();user.setAge(20);user.setEmail("user@atguigu.com");int result = userMapper.update(user, queryWrapper);System.out.println("受影响的行数:" + result);}//逻辑删除只能删除未删除状态的数据即is_deleted=0, 删除email为空的用户//UPDATE t_user SET is_deleted=1 WHERE is_deleted=0 AND (email IS NULL)QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.isNull("email");//条件构造器也可以构建删除语句的条件int result = userMapper.delete(queryWrapper);System.out.println("受影响的行数:" + result);}
}
UpdateWrapper
使用UpdateWrapper实现修改功能,不仅可以设置修改的条件还可以设置要修改的字段(不用再创建实体类对象),修改的字段和条件可以分开写也可以连这写
@springBootTest
public class MybatisPlusWrapperTest{@Autowiredprivate UserMapper userMapper;@Testpublic void test07() {//将(年龄大于20或邮箱为null)并且用户名中包含有a的用户信息修改UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();//设置修改的字段(以下两个updateWrapper可以连着写)updateWrapper.set("age", 18).set("email", "user@atguigu.com");//设置修改的条件, lambda表达式内的逻辑优先运算updateWrapper.like("user_name", "a").and(i -> i.gt("age", 20).or().isNull("email"));//UPDATE t_user SET age=?,email=? WHERE is_deleted=0 AND (user_name LIKE ? AND(age > ? OR email IS NULL))//由于之前设置了修改的字段,所以此时不再需要实体类,传递的参数为nullint result = userMapper.update(null, updateWrapper);System.out.println(result);}
}
condition组装条件
在真正开发的过程中,组装条件是常见的功能,而这些条件数据来源于用户输入,因此我们在组装这些条件时,必须先判断用户是否选择了这些条件,若选择则需要组装该条件,若没有选择则一定不能组装,以免影响SQL执行的结果
@springBootTest
public class MybatisPlusWrapperTest{@Autowiredprivate UserMapper userMapper;//调用条件构造器方法时不使用boolean类型的condition条件@Testpublic void test08() {//定义查询条件,有可能为null(用户未输入或未选择)String username = null;Integer ageBegin = 10;Integer ageEnd = 24;QueryWrapper<User> queryWrapper = new QueryWrapper<>();//StringUtils是由Mybatis-pius提供的工具类,isNotBlank()判断某字符串是否不为空且长度不为0且不由空白符(whitespace)构成if(StringUtils.isNotBlank(username)){queryWrapper.like("user_name",username);}if(ageBegin != null){queryWrapper.ge("age", ageBegin);}if(ageEnd != null){queryWrapper.le("age", ageEnd);}//由于username为null所以只有年龄作为条件//SELECT id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (age >=? AND age <= ?)List<User> users = userMapper.selectList(queryWrapper);users.forEach(System.out::println);}//调用条件构造器方法时使用boolean类型的condition条件@Testpublic void test08UseCondition() {//定义查询条件,有可能为null(用户未输入或未选择)String username = null;Integer ageBegin = 10;Integer ageEnd = 24;QueryWrapper<User> queryWrapper = new QueryWrapper<>();//方法的参数传入一个boolean类型的condition条件,如果条件为true则组装后面的条件queryWrapper.like(StringUtils.isNotBlank(username), "username", username).ge(ageBegin != null, "age", ageBegin) .le(ageEnd != null, "age", ageEnd);//SELECT id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (age >=? AND age <= ?)List<User> users = userMapper.selectList(queryWrapper);users.forEach(System.out::println);}
}
LambdaQueryWrapper/UpdateWrapper
@Data
public class User {private Long id;@TableFiled("user_name")private String name;private Integer age;private String email;@TableLogicprivate Integer isDeleted;
}
当使用字符串描述一个字段时容易写错出现运行时错误,使用一个函数式接口SFunction(实体类,?)访问实体类某个属性,底层自动获取该属性对应字段作为条件
@springBootTest
public class MybatisPlusWrapperTest{@Autowiredprivate UserMapper userMapper;//使用LambdaQueryWrapper查询@Testpublic void test09() {//定义查询条件,有可能为null(用户未输入)String username = "a";Integer ageBegin = 10;Integer ageEnd = 24;LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.like(StringUtils.isNotBlank(username), User::getName, username).ge(ageBegin != null, User::getAge, ageBegin).le(ageEnd != null, User::getAge, ageEnd);//SELECT id,user_name AS name,age,email,is_deleted FROM t_user WHERE (user_name LIKE ? age >=? AND age <= ?)List<User> users = userMapper.selectList(queryWrapper);users.forEach(System.out::println);}//使用LambdaUpdateWrapper@Testpublic void test10() {//组装set子句设置修改的条件和字段LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();updateWrapper.set(User::getAge, 18).set(User::getEmail, "user@atguigu.com").like(User::getName, "a").and(i -> i.lt(User::getAge, 24).or().isNull(User::getEmail)); User user = new User();//UPDATE t_user SET age=?,email=? WHERE is_deleted=0 AND (user_name LIKE ? AND(age > ? OR email IS NULL))int result = userMapper.update(user, updateWrapper);System.out.println("受影响的行数:" + result);}
}
插件
查
MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能
- 分页的本质是我们先写了查询功能,MyBatis Plus对我们的查询操作进行拦截然后增加一些额外的操作达到分页的功能
@Configuration
@MapperScan("com.atguigu.mybatisplus.mapper") //可以将主类中的扫描接口的注解移到此处
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {// 配置MyBatis Plus中插件的对象MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 在插件对象中添加内部插件(拦截器)并设置数据库类型interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}
}
@springBootTest
public class MybatisPlusPluginsTest{@Autowiredprivate UserMapper userMapper;@Testpublic void testPage(){//在分页对象中设置分页参数,当前页码数和每页显示的记录数Page<User> page = new Page<>(1, 5);//方法的参数是继承了IPage的类型Page和查询的条件构造器queryWrapper,将查询结果都封装到了分页对象当中//SELECT id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 LIMIT ? ?userMapper.selectPage(page, null);//获取分页数据List<User> list = page.getRecords();//获取分页相关的数据设置分页相关的超链接和按钮list.forEach(System.out::println);System.out.println("当前页:"+page.getCurrent());System.out.println("每页显示的条数:"+page.getSize());System.out.println("总记录数(未删除状态的数据):"+page.getTotal());System.out.println("总页数:"+page.getPages());System.out.println("是否有上一页:"+page.hasPrevious());System.out.println("是否有下一页:"+page.hasNext());}
}
自定义查询语句实现分页
自定义查询语句通过分页插件实现分页功能: 根据年龄查询用户列表,分页显示
//UserMapper中自定义接口方法
@Repository
public interface UserMapper extends BaseMapper<User> {//page表示分页对象,xml中的占位符可以从里面进行取值,传递参数Page必须放在第一位表示开启自动分页功能Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);
}
spring:# 配置数据源信息datasource:# 配置数据源类型type: com.zaxxer.hikari.HikariDataSource# 配置连接数据库的各个信息driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&userSSL=falseusername: rootpassword: 123456
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl# 设置MyBatis-Plus的全局配置global-config:db-config:# 设置实体类所对应的表的统一前缀table-prefix: t_# 设置统一的主键生成策略id-type: auto# 配置类型别名所对应的包,默认的别名是简类名且不区分大小写type-aliases-package: com.atguigu.mybatisplus.pojo# 扫描通用枚举的包type-enums-package: com.atguigu.mybatisplus.enums
UserMapper.xml中编写SQL
<!--SQL片段,记录基础字段-->
<sql id="BaseColumns">id,username,age,email</sql>
<!--IPage<User> selectPageVo(Page<User> page, Integer age);-->
<select id="selectPageVo" resultType="User">SELECT <include refid="BaseColumns"></include> FROM t_user WHERE age > {age}
</select><?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mybatisplus.mapper.UserMapper"><!--IPage<User> selectPageVo(Page<User> page, Integer age);--><select id="selectPageVo" resultType="User">SELECT id,user_name,age,email FROM t_user WHERE age > #{age}</select>
</mapper>
@Test
public void testSelectPageVo(){//设置分页参数Page<User> page = new Page<>(1, 5);userMapper.selectPageVo(page, 20);//获取分页数据List<User> list = page.getRecords();list.forEach(System.out::println);System.out.println("当前页:"+page.getCurrent());System.out.println("每页显示的条数:"+page.getSize());System.out.println("总记录数:"+page.getTotal());System.out.println("总页数:"+page.getPages());System.out.println("是否有上一页:"+page.hasPrevious());System.out.println("是否有下一页:"+page.hasNext());
}
乐观锁与悲观锁
模拟修改冲突
数据库中增加商品表并插入一条数据
CREATE TABLE t_product(
id BIGINT(20) NOT NULL COMMENT '主键ID',
NAME VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',
price INT(11) DEFAULT 0 COMMENT '价格',
VERSION INT(11) DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id));
--插入一条数据
INSERT INTO t_product (id, NAME, price) VALUES (1, '外星人笔记本', 100);
实体类和mapper接口
import lombok.Data;
@Data
public class Product {private Long id;private String name;private Integer price;private Integer version;
}
@Repository
public interface ProductMapper extends BaseMapper<Product> {
}
@Test
public void testConcurrentUpdate() {//小李取出的价格100Product p1 = productMapper.selectById(1L);System.out.println("小李取出的价格:" + p1.getPrice());//小王取出的价格100Product p2 = productMapper.selectById(1L);System.out.println("小王取出的价格:" + p2.getPrice());//小李将价格加了50元,存入了数据库p1.setPrice(p1.getPrice() + 50);int result1 = productMapper.updateById(p1);System.out.println("小李修改结果:" + result1);//小王将商品减了30元,存入了数据库p2.setPrice(p2.getPrice() - 30);int result2 = productMapper.updateById(p2);System.out.println("小王修改结果:" + result2);//最后的价格70Product p3 = productMapper.selectById(1L);System.out.println("最后的结果:" + p3.getPrice());
}
乐观锁实现流程
第一步: 数据库中添加version字段
第二步:取出记录时同时获取当前记录的版本号
SELECT id,`name`,price,`version`FROM product WHEREid = 1;
第三步更新: version + 1,如果where语句中的version版本不对即在我更新之前这行记录的版本号已经发生了改变,则更新失败
UPDATE product SET price = price+50, `version` = `version` + 1 WHERE id=1 AND `version`=1
Mybatis-Plus实现乐观锁
修改实体类添加@Version注解表示乐观锁版本号字段
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;
@Data
public class Product {private Long id;private String name;private Integer price;@Version//表示乐观锁版本号字段private Integer version;
}
添加乐观锁插件配置
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();//添加分页插件interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//添加乐观锁插件interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return interceptor;
}
@Test
public void testConcurrentUpdate() {//小李取出的价格100Product p1 = productMapper.selectById(1L);System.out.println("小李取出的价格:" + p1.getPrice());//小王取出的价格100Product p2 = productMapper.selectById(1L);System.out.println("小王取出的价格:" + p2.getPrice());//小李将价格加了50元,存入了数据库p1.setPrice(p1.getPrice() + 50);//update t_product set name = ?,price = ?,version = ?(原版本号+1) where id = ? and version = ?int result1 = productMapper.updateById(p1);System.out.println("小李修改结果:" + result1);//小王将商品减了30元,存入数据库时发现与取出数据的版本号不一致故更新失败p2.setPrice(p2.getPrice() - 30);//此时执行时版本号发生了改变,更新失败int result2 = productMapper.updateById(p2);System.out.println("小王修改结果:" + result2);//最后的价格150Product p3 = productMapper.selectById(1L);System.out.println("最后的结果:" + p3.getPrice());
}
优化修改流程
@Test
public void testConcurrentVersionUpdate() {//小李取数据Product p1 = productMapper.selectById(1L);//小王取数据Product p2 = productMapper.selectById(1L);//小李修改 + 50p1.setPrice(p1.getPrice() + 50);int result1 = productMapper.updateById(p1);System.out.println("小李修改的结果:" + result1);//小王修改 - 30p2.setPrice(p2.getPrice() - 30);int result2 = productMapper.updateById(p2);System.out.println("小王修改的结果:" + result2);if(result2 == 0){//失败重试,重新获取version并更新p2 = productMapper.selectById(1L);p2.setPrice(p2.getPrice() - 30);result2 = productMapper.updateById(p2);}System.out.println("小王修改重试的结果:" + result2);//最后的价格120Product p3 = productMapper.selectById(1L);System.out.println("老板看价格:" + p3.getPrice());
}
代码生成器
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.5.1</version>
</dependency>
<dependency><groupId>org.freemarker</groupId><artifactId>freemarker</artifactId><version>2.3.31</version>
</dependency>
public class FastAutoGeneratorTest {public static void main(String[] args) {FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3306/mybatis_plus?characterEncoding=utf-8&userSSL=false", "root", "123456").globalConfig(builder -> {builder.author("atguigu") // 设置作者//.enableSwagger() // 开启 swagger 模式.fileOverride() // 覆盖已生成文件.outputDir("D://mybatis_plus"); // 指定输出目录}).packageConfig(builder -> {builder.parent("com.atguigu") // 设置父包名.moduleName("mybatisplus") // 设置父包模块名.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://mybatis_plus"));// 设置mapperXml生成路径}).strategyConfig(builder -> {builder.addInclude("t_user") // 设置需要生成的表名.addTablePrefix("t_", "c_"); // 设置过滤表前缀}).templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板.execute();}
}
多数据源
步骤
适用于多种场景:纯粹多库(操作的表分布在不同数据库当中)、 读写分离(有的数据库负责读的功能,有的数据库负责写的功能)、 一主多从、 混合模式等
第一步: 创建mybatis_plus数据库和user表,mybatis_plus_1数据库和product表
--创建mybatis_plus数据库
CREATE DATABASE `mybatis_plus` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
use `mybatis_plus`;
--创建user表
CREATE TABLE `user` (`id` bigint(20) NOT NULL COMMENT '主键ID',`name` varchar(30) DEFAULT NULL COMMENT '姓名',`age` int(11) DEFAULT NULL COMMENT '年龄',`email` varchar(50) DEFAULT NULL COMMENT '邮箱',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--向user表中插入数据
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');
--创建mybatis_plus_1数据库
CREATE DATABASE `mybatis_plus_1` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
use `mybatis_plus_1`;
--创建product表
CREATE TABLE product(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',
price INT(11) DEFAULT 0 COMMENT '价格',
version INT(11) DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id)
);
--向product表插入数据
INSERT INTO product (id, NAME, price) VALUES (1, '外星人笔记本', 100);
第二步:引入依赖
<dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.5.0</version>
</dependency>
第三步: 配置多数据源, 注释掉之前的数据库连接, 添加新配置
spring:# 配置数据源信息datasource:dynamic:# 设置默认的数据源或者数据源组,默认值即为masterprimary: master# 严格匹配数据源,默认false.true表示未匹配到指定数据源时抛异常,false表示未匹配到使用默认数据源strict: falsedatasource:master:url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=falsedriver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: 123456slave_1:url: jdbc:mysql://localhost:3306/mybatis_plus_1?characterEncoding=utf-8&useSSL=falsedriver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: 123456
第四步: 创建实体类封装表中查询的数据
//封装产品信息的实体类
@Data
public class Product {private Integer id;private String name;private Integer price;private Integer version;
}
//封装用户信息的实体类
@Data
public class User {private Integer id;private String userName;private Integer age;private Integer sex;private String email;private Integer isDeleted;
}
第五步: 编写mapper接口
@Repository
public interface ProductMapper extends BaseMapper<Product> {
}
@Repository
public interface UserMapper extends BaseMapper<User> {
}
第六步: 创建商品和用户service和其实现类,使用 @DS注解切换数据源
- @Ds可以注解在方法上或类上(类中所有的方法默认都有该注解),同时存在就近原则方法上注解优先于类上注解
注解 | 结果 |
---|---|
没有@DS注解 | 操作的是默认的数据源 |
@DS(dsName”) | dsName可以为组名也可以为具体某个库的名称 |
//用户service和其实现类
public interface UserService extends IService<User> {}
@DS("master") //指定所操作的数据源,user表在master数据源当中
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implementsUserService {
}
//商品service和其实现类
public interface ProductService extends IService<Product> {}
@DS("slave_1")//product表在slave_1数据源当中
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {
}
第七步: 编写Spring Boot主程序的启动类
@SpringBootApplication
@MapperScan("com.atguigu.mybatisplus.mapper")
public class MybatisPlusDatasourceApplication {public static void main(String[] args) {SpringApplication.run(MybatisPlusDatasourceApplication.class, args);}
}
第八步: 由于每个库都有一张表,如果通过一个测试可以分别获取到用户数据与商品数据成功说明多库模拟成功
- 如果我们实现读写分离,将写操作方法加上主库数据源,读操作方法加上从库数据源,自动切换就能实现读写分离
@SpringBootTest
public class MybatisPlusDatasourceApplicationTests {@Autowiredprivate UserService userService;@Autowiredprivate ProductService productService;@Testpublic void testDynamicDataSource(){//都能顺利获取对象System.out.println(userService.getById(1));System.out.println(productService.getById(1));}
}
MyBatisX插件
在真正开发过程中,对于一些复杂的SQL,多表联查,我们就需要自己去编写代码和SQL语句,这个时候可以使用MyBatisX插件帮助我们
MyBatisX一款基于 IDEA 的快速开发插件, 打开IDEA进入 File -> Settings -> Plugins -> 输入mybatisx搜索并安装然后重启IDEA, 插件具体用法查看官网
跳转功能
由于一个项目中的mapper接口和映射文件有很多找起来很麻烦, 而MyBatisX可以快速找到mapper接口对应的映射文件以及映射文件对应的mapper接口
生成代码
第一部: 创建一个Spring Boot工程并引入相关依赖, 在application.yml文件中配置数据源的连接信息
spring:# 配置数据源信息datasource:# 配置数据源类型(Spring boot默认使用的数据源)type: com.zaxxer.hikari.HikariDataSource# 配置连接数据库的各个信息driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&userSSL=falseusername: rootpassword: 123456
第二步: 在IDEA的Database中连接本地数据库
第三步: 使用MyBatisX插件生成表相关的mapper,service,映射文件
最终生成的目录结构
根据mapper接口中的自定义的模板方法名在SQL映射文件中生成对应的增删改查的SQL语句,自动将mapper接口中的方法与映射文件的SQL语句相关联
public interface UserMapper extends BaseMapper<User> {//添加数据时,如果实体类属性为null则不添加该属性对应的字段int insertSelective(User user);int deleteByUidAndUserName(@Param("uid") Long uid, @Param("userName") String userName);int updateAgeAndSexByUid(@Param("age") Integer age, @Param("sex") Integer sex, @Param("uid") Long uid);List<User> selectAgeAndSexByAgeBetween(@Param("beginAge") Integer beginAge, @Param("endAge") Integer endAge);List<User> selectAllOrderByAgeDesc();
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mybatisx.mapper.UserMapper"><resultMap id="BaseResultMap" type="com.atguigu.mybatisx.pojo.User"><id property="uid" column="uid" jdbcType="BIGINT"/><result property="userName" column="user_name" jdbcType="VARCHAR"/><result property="age" column="age" jdbcType="INTEGER"/><result property="email" column="email" jdbcType="VARCHAR"/><result property="sex" column="sex" jdbcType="INTEGER"/><result property="isDeleted" column="is_deleted" jdbcType="INTEGER"/></resultMap><!--SQL片段,所有字段--><sql id="Base_Column_List">uid,user_name,age,email,sex,is_deleted</sql><!--int insertSelective(User user);--><insert id="insertSelective">insert into t_user<trim prefix="(" suffix=")" suffixOverrides=","><if test="uid != null">uid,</if><if test="userName != null">user_name,</if><if test="age != null">age,</if><if test="email != null">email,</if><if test="sex != null">sex,</if><if test="isDeleted != null">is_deleted,</if></trim>values<trim prefix="(" suffix=")" suffixOverrides=","><if test="uid != null">#{uid,jdbcType=BIGINT},</if><if test="userName != null">#{userName,jdbcType=VARCHAR},</if><if test="age != null">#{age,jdbcType=INTEGER},</if><if test="email != null">#{email,jdbcType=VARCHAR},</if><if test="sex != null">#{sex,jdbcType=INTEGER},</if><if test="isDeleted != null">#{isDeleted,jdbcType=INTEGER},</if></trim></insert><delete id="deleteByUidAndUserName">delete from t_user where uid = #{uid,jdbcType=NUMERIC} AND user_name = #{userName,jdbcType=VARCHAR}</delete><update id="updateAgeAndSexByUid">update t_user set age = #{age,jdbcType=NUMERIC},sex = #{sex,jdbcType=NUMERIC} where uid = #{uid,jdbcType=NUMERIC}</update><select id="selectAgeAndSexByAgeBetween" resultMap="BaseResultMap">select age, sex from t_user where age between #{beginAge,jdbcType=INTEGER} and #{endAge,jdbcType=INTEGER}</select><select id="selectAllOrderByAgeDesc" resultMap="BaseResultMap">select <include refid="Base_Column_List"/> from t_user order by age desc</select>
</mapper>
相关文章:
(3)MyBatis-Plus待开发
常用注解 TableName MyBatis-Plus在确定操作的表时,由BaseMapper的泛型决定即实体类型决定,且默认操作的表名和实体类型的类名一致,如果不一致则会因找不到表报异常 //向表中插入一条数据 Test public void testInsert(){User user new User(null, &…...
正则表达式参考手册
修饰符 修饰符用于执行区分大小写和全局匹配: 修饰符描述i执行对大小写不敏感的匹配。g执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。m执行多行匹配。 方括号 方括号用于查找某个范围内的字符: 表达式描述[abc]查找方括号之间…...
【农业生产模拟】WOFOST模型与PCSE模型实践
查看原文>>>【农业生产模拟】WOFOST模型与PCSE模型实践 实现作物产量的准确估算对于农田生态系统响应全球变化、可持续发展、科学粮食政策制定、粮食安全维护都至关重要。传统的经验模型、光能利用率模型等估产模型原理简单,数据容易获取,但是…...
PHP8中获取并删除数组中最后一个元素-PHP8知识详解
在php8中,array_pop()函数将返回数组的最后一个元素,并且将该元素从数组中删除。语法格式如下: array_pop(目标数组) 获取并删除数组中最后一个元素,参考代码: <?php $stu array(s001>明明,s002>亮亮,s…...
JS原理-笔记(1/3)
JS原理-笔记(1/3) 知识点自测 今天课程中涉及到的已学习知识点 函数的call方法-文档链接 // 以指定的this调用函数,并通过 从第二个参数开始依次传递参数 function func(food,drink){console.log(this)console.log(food)console.log(drink)…...
Django创建应用、ORM的进阶使用及模型类数据库迁移
1 Django项目创建第一个应用 Django 项目就是基于 Django 框架开发的 Web 应用,它包含了一组配置和多个应用,我们把应用称之为 App,在前文中对它也做了相应的介绍,比如 auth、admin,它们都属于 APP。 一个 App 就是一…...
tcpdump 如何使用
tcpdump 是一个在Unix和类Unix系统上运行的网络抓包工具,它用于捕获网络流量并将其转储到文件中以供后续分析。tcpdump非常强大,可以用于监控、调试和分析网络通信,用于排查网络问题以及安全审计。以下是关于如何使用tcpdump的详细介绍&#…...
goweb入门
创建gomod项目 go mod init web01新建main.go package mainimport ("fmt""net/http" )func handler(writer http.ResponseWriter, request *http.Request) {fmt.Fprintf(writer, "Hello World,%s!", request.URL.Path[1:]) } func main() {fmt…...
【python爬虫】批量识别pdf中的英文,自动翻译成中文下
不管是上学还是上班,有时不可避免需要看英文文章,特别是在写毕业论文的时候。比较头疼的是把专业性很强的英文pdf文章翻译成中文。我记得我上学的时候,是一段一段复制,或者碰到不认识的单词就百度翻译一下,非常耗费时间。之前的文章提供了批量识别pdf中英文的方法,详见【…...
YApi 新版如何查看 http 请求数据
YApi 新版如何查看 http 请求数据 因chrome 安全策略限制,在 cross-request 升级到 3.0 后, 不再支持文件上传功能,并且需要通过以下方法查看 network:1.首先在chrome 输入 > chrome://extensions打开扩展页2.开启开发者模式3.点击 cross…...
自动驾驶(apollo)
💓博主csdn个人主页:小小unicorn 🚚代码仓库:小小unicorn的代码仓库🚚 🌹🌹🌹关注我带你学习编程知识 自动驾驶技术 引言自动驾驶的基本原理自动驾驶的技术挑战自动驾驶的潜在影响结…...
web3.0涉及的技术
非同质化代币 非同质化代币(Non-Fungible Tokens,NFTs)是一种数字资产,与传统的加密货币(如比特币或以太币)不同,它们具有独特性和不可替代性。NFTs 是基于区块链技术的数字资产,用…...
26. 不相同的字符串(第一期模拟笔试)
题目:样例: 输入 1 abab 输出 2 思路: 这里的题目要求我们要最少操作删除次数,我们可以先统计每个字符个数,然后开始删除,每操作删除一次,就会产生一个新字符,ans r[i] >> 1…...
Rethink LSTMGRU
LSTM 设计思想 姑且不看偏置。 W W W 和 U U U 是加权的矩阵,写模型的时候用 nn.Linear(in_dim, out_dim) 就成; σ \sigma σ 是 Sigmoid 函数 第一条,遗忘门,定义为 有多少内容需要被遗忘;第二条:输入门…...
状态管理艺术——借助Spring StateMachine驭服复杂应用逻辑
文章目录 1. 什么是状态2. 有限状态机概述3. Spring StateMachine4. Spring StateMachine 入门小案例4.1 接口测试 5. 总结 1. 什么是状态 在开发中,无时无刻离不开状态的一个概念,任何一条数据都有属于它的状态。 比如一个电商平台,一个订…...
获取和设置小程序和h5的页面栈
获取页面栈: 语法: let pages getCurrentPages(); 设置页面栈: 小程序语法: pages.data H5语法: pages let pages getCurrentPages(); let page pages[pages.length - 2]; if(page.route "pages/conf…...
Mysql基于成本选择索引
本篇文章介绍mysql基于成本选择索引的行为,解释为什么有时候明明可以走索引,但mysql却没有走索引的原因 mysql索引失效的场景大致有几种 不符合最左前缀原则在索引列上使用函数或隐式类型转换使用like查询,如 %xxx回表代价太大索引列区分度过…...
Element-ui container常见布局
1、header\main布局 <template> <div> <el-container> <el-header>Header</el-header> <el-main>Main</el-main> </el-container> </div> </template> <style> .el-header { …...
ssm实现折线统计图
方法1:单张数据表中的数据图表生成 图表统计,查看部门人数统计这里实现的时单张表中的数据实现部门人数折线统计图展示。 <script type"text/javascript">// 利用AjAx来获取后台传入的数据(Responsebody注解传入的&…...
GLSL ES着色器 精度限定字
目录 前言 WebGL支持的三种精度 数据类型的默认精度 float类型没有默认精度 预处理指令 在GLSL ES中常用的三种预处理指令。 预定义的内置宏 前言 GLSL ES新引入了精度限定字,目的是帮助着色器程序提高运行效率,削减内存开支。顾名思义…...
webrtc的FULL ICE和Lite ICE
1、ICE的模式 分为FULL ICE和Lite ICE: FULL ICE:是双方都要进行连通性检查,完成的走一遍流程。 Lite ICE: 在FULL ICE和Lite ICE互通时,只需要FULL ICE一方进行连通性检查, Lite一方只需回应response消息。这种模式对于部署在公网…...
flink的几种常见的执行模式
背景 在运行flink时,我们经常会有几种不同的执行模式,比如在IDE中启动时,通过提交到YARN上,还有通过Kebernates启动时,本文就来记录一下这几种模式 flink的几种执行模式 flink嵌入式模式: 这是一种我们在…...
蓝桥杯备赛Day8——队列
大家好,我是牛哥带你学代码,本专栏详细介绍了蓝桥杯备赛的指南,特别适合迎战python组的小白选手。专栏以天作为单位,定期更新,将会一直更新,直到所有数据结构相关知识及高阶用法全部囊括,欢迎大家订阅本专栏! 队列也属于基础数据结构。 队列概念 队列是一种数据结构,…...
用滑动条做调色板---cv2.getTrackbarPos(),cv2.creatTrackbar()
滑动轨迹栏作调色板 cv.createTrackbar(‘R’, ‘image’, 0, 255, nothing) 参数:哪个滑动轨迹栏,哪个窗口,最小值,最大值,回调函数 cv.getTrackbarPos(‘R’, ‘image’) 参数:轨迹栏名,窗口…...
dubbo 服务注册使用了内网IP,而服务调用需要使用公网IP进行调用
一、问题描述: 使用dubbo时,提供者注册时显示服务地址ip为[内网IP:20880],导致其他消费者在外部连接的情况下时,调用dubbo服务失败 二、解决办法 方法一、修改hosts文件 (1). 先查询一下服务器的hostna…...
外传-Midjourney的局部重绘功能
今天在抄袭。。。啊不,借鉴 midjourney 官网教程的时候,发现多了一个 局部重绘的功能,意外发觉还不错,分享一下用法。 先给大家说一下,我这段时间都在学习 SD,局部重绘是基操,而 MJ 一直是次次…...
Spring Boot 中使用 Poi-tl 渲染数据并生成 Word 文档
本文 Demo 已收录到 demo-for-all-in-java 项目中,欢迎大家 star 支持!后续将持续更新! 前言 产品经理急冲冲地走了过来。「现在需要将按这些数据生成一个 Word 报告文档,你来安排下」 项目中有这么一个需求,需要将用户…...
Java基础(二十一)十点半游戏
十点半游戏 十点半是一种流行的纸牌游戏,可以说是一种变体的二十一点游戏。游戏的规则是,每个玩家根据所拿到的牌点数的总和来决定是否继续要牌。目标是尽量接近但不超过十点半的点数,超过十点半即为爆牌。如果两名玩家都未爆牌,…...
第8节-PhotoShop基础课程-常用快捷键汇总
文章目录 前言1.工具栏1.移动工具 V2.矩形框选工具 M3.套索工具 L4.魔棒工具 W5.裁剪工具 C6.吸管工具 I7.污点修复工具 J8.仿制图章工具 S9.历史记录画笔工具 Y10.橡皮檫工具 E11.油漆桶工具 G12 减淡工具 O13.钢笔工具 P14 横排文字工具 T15.路径选择工具 A16 椭圆工具 U17 抓…...
Redis带你深入学习数据类型set
目录 1、set 2、set相关命令 2.1、添加元素 sadd 2.2、获取元素 smembers 2.3、判断元素是否存在 sismember 2.4、获取set中元素数量 scard 2.5、删除元素spop、srem 2.6、移动元素smove 2.7、集合中相关命令:sinter、sinterstore、sunion、sunionstore、s…...
中国建筑装饰网注册用户名/优化神马网站关键词排名价格
文章目录1. 按2. 代码1. 按 判断两个文件是否相同需要用到filecmp模块: filecmp.cmp(f1, f2, shallowTrue) 比较名为f1和f2的文件,如果它们似乎相等则返回True,否则返回False 如果shallow为真,那么具有相同os.stat()签名的文件将…...
wordpress限制次数/网络服务器配置与管理
p10 基础模块——修改mapper文件 p11 基础模块——搭建Spring的单元测试环境 其中 Department.java package com.atguigu.crud.bean;public class Department {private Integer deptId;private String deptName;public Department() {}public Department(Integer deptId, St…...
做网站建设的企业/手机版百度入口
本文主要内容:见下文目录 目录 1、Matplotlib之父简介 2、matplotlib图形结构 figure层 axes层...
做门户网站用什么程序/链接平台
进入终端首先是这样的: 要输入cd /才能进入根目录: 然后即可通过ls查看文件了:...
深圳求做网站/品牌运营
1. 使用原生input的标签,type为file <template><form><inputrefupload_sheetplaceholder"请输入内容"type"file"accept"*"id"sheet_form"click"resetValue"change"temUpload($event)"/></f…...
网站风险怎么解决方案/手机百度快照
案例一:去除集合里面的重复元素 package cn.itcast_04;import java.util.ArrayList;/** ArrayList去除集合中字符串的重复值(字符串的内容相同)* * 分析:* A:创建集合对象* B:添加多个字符串元素(包含内容相同的)* C:创建新集合* D:遍历旧集合,获…...