主要是黑马商城的学习笔记
MybatisPlus
1、配置数据库源
修改application.yaml
1 2 3 4 5 6 7 8 9 10 11 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 123456 logging: level: com.itheima: debug pattern: dateformat: HH:mm:ss
2、引入依赖
1 2 3 4 5 <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-boot-starter</artifactId > <version > 3.5.3.1</version > </dependency >
3、定义Mapper
MybatisPlus提供了一个基础的BaseMapper接口,我们只需自定义实现这个接口
1 2 3 4 5 6 7 public interface UserMapper extends BaseMapper <User> { void updateBalanceByIds (@Param("ew") QueryWrapper<User> queryWrapper,@Param("amount") int amount) ; @Update("UPDATE user SET balance = balance-#{money} where id=#{id}") void deductBalance (@Param("id") Long id,@Param("money") Integer money) ; }
4、测试
部分代码,其他用法相似
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Autowired private UserMapper userMapper;@Test void testInsert () { User user = new User (); user.setId(5L ); user.setUsername("Lucy" ); user.setPassword("123" ); user.setPhone("18688990011" ); user.setBalance(200 ); user.setInfo(UserInfo.of(24 ,"英文老师" ,"female" )); user.setCreateTime(LocalDateTime.now()); user.setUpdateTime(LocalDateTime.now()); userMapper.insert(user); }
5、常用注解介绍
@TableName
描述:表名注解,标识实体类对应的表
使用位置:实体类
1 2 3 4 5 @TableName("user") public class User { private Long id; private String name; }
属性
类型
必须指定
默认值
描述
value
String
否
“”
表名
schema
String
否
“”
schema
keepGlobalPrefix
boolean
否
false
是否保持使用全局的 tablePrefix 的值(当全局 tablePrefix 生效时)
resultMap
String
否
“”
xml 中 resultMap 的 id(用于满足特定类型的实体类对象绑定)
autoResultMap
boolean
否
false
是否自动构建 resultMap 并使用(如果设置 resultMap 则不会进行 resultMap 的自动构建与注入)
excludeProperty
String[]
否
{}
需要排除的属性名 @since 3.3.1
@TableId
描述:主键注解,标识实体类中的主键字段
使用位置:实体类的主键字段
1 2 3 4 5 6 @TableName("user") public class User { @TableId private Long id; private String name; }
属性
类型
必须指定
默认值
描述
value
String
否
“”
表名
type
Enum
否
IdType.NONE
指定主键类型
@TableField
解决变量名和数据库字段不一致问题、解决变量名和数据库一致,但是和数据库关键词冲突。
1 2 3 4 5 6 7 8 9 10 11 @TableName("user") public class User { @TableId private Long id; private String name; private Integer age; @TableField("isMarried") private Boolean isMarried; @TableField("concat") private String concat; }
高级注解参数自行百度
6、常见配置
实体类的别名扫描包
1 2 3 4 5 mybatis-plus: type-aliases-package: com.itheima.mp.domain.po global-config: db-config: id-type: auto
MyBatisPlus支持手写SQL的,mapper文件的读取地址可以自己配置
1 2 mybatis-plus: mapper-locations: "classpath*:/mapper/**/*.xml"
7、条件构造器(仅供参考)
QueryWrapper
1 2 3 4 5 6 7 QueryWrapper<User> wrapper = new QueryWrapper <User>() .select("id" , "username" , "info" , "balance" ) .like("username" , "o" ) .ge("balance" , 1000 ); List<User> users = userMapper.selectList(wrapper);
UpdateWrapper
更新id为1,2,4的用户的余额,扣200
1 UPDATE user SET balance = balance - 200 WHERE id in (1 , 2 , 4 )
SET的赋值结果是基于字段现有值的,这个时候就要利用UpdateWrapper中的setSql功能
1 2 3 4 5 6 7 8 List<Long> ids = List.of(1L , 2L , 4L ); UpdateWrapper<User> wrapper = new UpdateWrapper <User>() .setSql("balance = balance - 200" ) .in("id" , ids); userMapper.update(null , wrapper);
LambdaQueryWrapper
减少硬编码
LambdaQueryWrapper
LambdaUpdateWrapper
①
1 2 3 4 5 6 7 8 9 QueryWrapper<User> wrapper = new QueryWrapper <>(); wrapper.lambda() .select(User::getId, User::getUsername, User::getInfo, User::getBalance) .like(User::getUsername, "o" ) .ge(User::getBalance, 1000 ); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println);
②
1 2 3 4 5 6 7 LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper <User>(); lambdaWrapper.select(User::getId,User::getUsername,User::getInfo,User::getBalance); lambdaWrapper.like(User::getUsername,"o" ); lambdaWrapper.ge(User::getBalance,1000 ); List<User> users = userMapper.selectList(lambdaWrapper); users.forEach(System.out::println);
8、自定义SQL
复杂SQL
比如我们需要动态的构造复杂SQL
1 2 3 4 5 6 7 8 9 @Test void testCustomWrapper () { List<Long> ids = List.of(1L , 2L , 4L ); QueryWrapper<User> wrapper = new QueryWrapper <User>().in("id" , ids); userMapper.deductBalanceByIds(200 , wrapper); }
1 2 3 4 public interface UserMapper extends BaseMapper <User> { @Select("UPDATE user SET balance = balance - #{money} ${ew.customSqlSegment}") void deductBalanceByIds (@Param("money") int money, @Param("ew") QueryWrapper<User> wrapper) ; }
多表关联
之前我们要查询出所有收货地址在北京的并且用户id在1、2、4之中的用户
1 2 3 4 5 6 7 8 9 10 <select id ="queryUserByIdAndAddr" resultType ="com.itheima.mp.domain.po.User" > SELECT * FROM user u INNER JOIN address a ON u.id = a.user_id WHERE u.id <foreach collection ="ids" separator ="," item ="id" open ="IN (" close =")" > #{id} </foreach > AND a.city = #{city} </select >
现在:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Test void testCustomJoinWrapper () { QueryWrapper<User> wrapper = new QueryWrapper <User>() .in("u.id" , List.of(1L , 2L , 4L )) .eq("a.city" , "北京" ); List<User> users = userMapper.queryUserByWrapper(wrapper); users.forEach(System.out::println); } @Select("SELECT u.* FROM user u INNER JOIN address a ON u.id = a.user_id ${ew.customSqlSegment}") List<User> queryUserByWrapper (@Param("ew") QueryWrapper<User> wrapper) ;
解释一下:我们不希望写复杂的多表查询,那么我们就利用Wrapper来生成代码。代码逻辑也很简单就是我们的需求,然后我们自己的mapper中添加联表代码,将复杂的查询部分用wrapper生成的代码替代。
思想就是把复杂的查询条件换掉,使用wrapper进行拼接
9、Service接口
MybatisPlus不仅提供了BaseMapper,还提供了通用的Service接口及默认实现,封装了一些常用的service模板方法。 通用接口为IService,默认实现为ServiceImpl,其中封装的方法可以分为以下几类:
save:新增
remove:删除
update:更新
get:查询单个结果
list:查询集合结果
count:计数
page:分页查询
用法
1 2 3 4 5 6 7 8 9 10 11 12 13 public interface IUserService extends IService <User> { void deductBalance (Long id, Integer money) ; List<User> queryUsers (String name, Integer status, Integer minBalance, Integer maxBalance) ; UserVO queryUserAndAddressById (Long id) ; List<UserVO> queryUserAndAddressByIds (List<Long> ids) ; PageDTO<UserVO> queryUsersPage (UserQuery userQuery) ; }
然后,编写UserServiceImpl类,继承ServiceImpl,实现UserService:
1 2 3 @Service public class UserServiceImpl extends ServiceImpl <UserMapper, User> implements IUserService {}
其余不详细介绍了,笔者认为不需要深入,会用即可
10、Service的批量新增功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Test void testSaveBatch () { List<User> list = new ArrayList <>(1000 ); long b = System.currentTimeMillis(); for (int i = 1 ; i <= 100000 ; i++) { list.add(buildUser(i)); if (i % 1000 == 0 ) { userService.saveBatch(list); list.clear(); } } long e = System.currentTimeMillis(); System.out.println("耗时:" + (e - b)); } private User buildUser (int i) { User user = new User (); user.setUsername("user_" + i); user.setPassword("123" ); user.setPhone("" + (18688190000L + i)); user.setBalance(2000 ); user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}" ); user.setCreateTime(LocalDateTime.now()); user.setUpdateTime(user.getCreateTime()); return user; }
可以发现其实MybatisPlus的批处理是基于PrepareStatement的预编译模式,然后批量提交,最终在数据库执行时还是会有多条insert语句,逐条插入数据。SQL类似这样:
1 2 3 4 Preparing: INSERT INTO user ( username, password, phone, info, balance, create_time, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ? ) Parameters: user_1, 123 , 18688190001 , "", 2000 , 2023 -07 -01 , 2023 -07 -01 Parameters: user_2, 123 , 18688190002 , "", 2000 , 2023 -07 -01 , 2023 -07 -01 Parameters: user_3, 123 , 18688190003 , "", 2000 , 2023 -07 -01 , 2023 -07 -01
优化方法: 修改项目中的application.yml文件,在jdbc的url后面添加参数&rewriteBatchedStatements=true:
1 2 3 4 5 6 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 123456
SQL被重写类似于这样
1 2 3 4 5 6 INSERT INTO user ( username, password, phone, info, balance, create_time, update_time )VALUES (user_1, 123 , 18688190001 , "", 2000 , 2023 -07 -01 , 2023 -07 -01 ), (user_2, 123 , 18688190002 , "", 2000 , 2023 -07 -01 , 2023 -07 -01 ), (user_3, 123 , 18688190003 , "", 2000 , 2023 -07 -01 , 2023 -07 -01 ), (user_4, 123 , 18688190004 , "", 2000 , 2023 -07 -01 , 2023 -07 -01 );
11、代码生成器
下载Idea插件MyBatisPlus
上方工具栏有先点击Config Database配置连接数据库。然后再点击Code Generator
配置好相关信息,生成代码
12、逻辑删除
顾名思义,不是真正的删除,而是在数据库表添加一个字段 判断此数据是否处于删除状态
1 2 ALTER TABLE address ADD deleted BIT DEFAULT b'0' NULL COMMENT '逻辑删除' ;
配置逻辑删除字段
1 2 3 4 5 6 mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0
执行自带的删除操作,可以看到效果一样,但是它会自动将deleted字段置为1表示已删除
Log:
1 2 3 4 5 22:34:47 INFO 21300 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... 22:34:48 INFO 21300 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. 22:34:48 DEBUG 21300 --- [ main] c.i.mp.mapper.AddressMapper.deleteById : ==> Preparing: UPDATE address SET deleted=1 WHERE id=? AND deleted=0 22:34:48 DEBUG 21300 --- [ main] c.i.mp.mapper.AddressMapper.deleteById : ==> Parameters: 59(Long) 22:34:48 DEBUG 21300 --- [ main] c.i.mp.mapper.AddressMapper.deleteById : <== Updates: 1
13、自定义枚举类型
使用场景:
比如,我们数据库有一个字段为Interger status(1正常 2冻结),在业务操作的时候自动将我们的**枚举类型(正常、冻结)**自动转成Interger类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Getter public enum UserStatus { NORMAL(1 , "正常" ), FREEZE(2 , "冻结" ) ; private final int value; private final String desc; UserStatus(int value, String desc) { this .value = value; this .desc = desc; } }
这个构造类其实是私有 的,方便 NORMAL(1, “正常”) 的构造
然后我们将status类型改为枚举类型
@EnumValue注解来标记枚举属性作为数据库的值,@JsonValue注解标记JSON序列化时(前端得到的)展示的字段:
配置枚举处理器(貌似3.5.2开始不用配置)
1 2 3 mybatis-plus: configuration: default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
预期结果,前端显示的信息是枚举的desc描述 :
同时呢数据库存储的则是用枚举的value存储的int类型
)
14、JSON类型处理器
问题发现:
数据库中存在一个JSON字段,例如
而对应存储的实体类的属性却是String类型
1 2 3 4 5 6 7 8 @Data @TableName(value = "user",autoResultMap = true) public class User { private String info; }
那么,我们在java中要读取 info的值的时候,就不方便,因为它是String对象,不是JSON对象。而我们如果将info改成Map或者是其他的对象,读取是方便了。将来存储到数据库的时候又要转成String对象,就很麻烦。 针对上述问题。我们引入了JSON类型处理器
解决思路:
替换info的类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Data @TableName(value = "user",autoResultMap = true) public class User { private UserInfo info; } @Data @NoArgsConstructor @AllArgsConstructor(staticName = "of") public class UserInfo { private Integer age; private String intro; private String gender; }
在字段上声明类型处理器
1 2 3 @TableField(typeHandler = JacksonTypeHandler.class) private UserInfo info;
之后则会自动向数据库中存JSON字符串。java代码中使用的时候则会自动将JSON转成UserInfo对象
15、分页插件
配置Mybatis配置项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Configuration public class MyBatisConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor () { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor (); PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor (DbType.MYSQL); paginationInnerInterceptor.setMaxLimit(1000L ); interceptor.addInnerInterceptor(paginationInnerInterceptor); return interceptor; } }
使用分页API
1 2 3 4 5 6 7 8 9 10 11 12 @Test void testPageQuery () { Page<User> p = userService.page(new Page <>(2 , 2 )); System.out.println("total = " + p.getTotal()); System.out.println("pages = " + p.getPages()); List<User> records = p.getRecords(); records.forEach(System.out::println); }
具体使用我觉得用不到。不需要了解。复杂。有更好的实现方法。可以用PageHelper。真的要整合MyBatisPlus的分页插件。ChatGpt后再去学习
16、代码自动生成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 public class ContentCodeGenerator { private static final String SERVICE_NAME = "content" ; private static final String DATA_SOURCE_USER_NAME = "root" ; private static final String DATA_SOURCE_PASSWORD = "mysql" ; private static final String[] TABLE_NAMES = new String []{ "course_base" , "course_market" , "teachplan" , "teachplan_media" , "course_teacher" , "course_category" , "course_publish" , "course_publish_pre" }; private static final Boolean IS_DTO = false ; public static void main (String[] args) { AutoGenerator mpg = new AutoGenerator (); mpg.setTemplateEngine(new FreemarkerTemplateEngine ()); GlobalConfig gc = new GlobalConfig (); gc.setFileOverride(true ); gc.setOutputDir(System.getProperty("user.dir" ) + "/xuecheng-plus-generator/src/main/java" ); gc.setAuthor("itcast" ); gc.setOpen(false ); gc.setSwagger2(false ); gc.setServiceName("%sService" ); gc.setBaseResultMap(true ); gc.setBaseColumnList(true ); if (IS_DTO) { gc.setSwagger2(true ); gc.setEntityName("%sDTO" ); } mpg.setGlobalConfig(gc); DataSourceConfig dsc = new DataSourceConfig (); dsc.setDbType(DbType.MYSQL); dsc.setUrl("jdbc:mysql://192.168.101.65:3306/xcplus_" + SERVICE_NAME + "?serverTimezone=UTC&useUnicode=true&useSSL=false&characterEncoding=utf8" ); dsc.setDriverName("com.mysql.cj.jdbc.Driver" ); dsc.setUsername(DATA_SOURCE_USER_NAME); dsc.setPassword(DATA_SOURCE_PASSWORD); mpg.setDataSource(dsc); PackageConfig pc = new PackageConfig (); pc.setModuleName(SERVICE_NAME); pc.setParent("com.xuecheng" ); pc.setServiceImpl("service.impl" ); pc.setXml("mapper" ); pc.setEntity("model.po" ); mpg.setPackageInfo(pc); TemplateConfig tc = new TemplateConfig (); mpg.setTemplate(tc); StrategyConfig strategy = new StrategyConfig (); strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); strategy.setEntityLombokModel(true ); strategy.setRestControllerStyle(true ); strategy.setInclude(TABLE_NAMES); strategy.setControllerMappingHyphenStyle(true ); strategy.setTablePrefix(pc.getModuleName() + "_" ); strategy.setEntityBooleanColumnRemoveIsPrefix(true ); strategy.setRestControllerStyle(true ); strategy.setTableFillList(Arrays.asList( new TableFill ("create_date" , FieldFill.INSERT), new TableFill ("change_date" , FieldFill.INSERT_UPDATE), new TableFill ("modify_date" , FieldFill.UPDATE) )); mpg.setStrategy(strategy); mpg.execute(); } }
Mybatis
1、递归查询
左联查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 <resultMap id ="treeNodeResultMap" type ="com.xuecheng.content.model.dto.TeachplanDto" > <id column ="one_id" property ="id" /> <result column ="one_pname" property ="pname" /> <result column ="one_parentid" property ="parentid" /> <result column ="one_grade" property ="grade" /> <result column ="one_mediaType" property ="mediaType" /> <result column ="one_startTime" property ="startTime" /> <result column ="one_endTime" property ="endTime" /> <result column ="one_orderby" property ="orderby" /> <result column ="one_courseId" property ="courseId" /> <result column ="one_coursePubId" property ="coursePubId" /> <collection property ="teachPlanTreeNodes" ofType ="com.xuecheng.content.model.dto.TeachplanDto" > <id column ="two_id" property ="id" /> <result column ="two_isPreview" property ="isPreview" /> <result column ="two_pname" property ="pname" /> <result column ="two_parentid" property ="parentid" /> <result column ="two_grade" property ="grade" /> <result column ="two_mediaType" property ="mediaType" /> <result column ="two_startTime" property ="startTime" /> <result column ="two_endTime" property ="endTime" /> <result column ="two_orderby" property ="orderby" /> <result column ="two_courseId" property ="courseId" /> <result column ="two_coursePubId" property ="coursePubId" /> <association property ="teachplanMedia" javaType ="com.xuecheng.content.model.po.TeachplanMedia" > <result column ="teachplanMeidaId" property ="id" /> <result column ="mediaFilename" property ="mediaFilename" /> <result column ="mediaId" property ="mediaId" /> <result column ="two_id" property ="teachplanId" /> <result column ="two_courseId" property ="courseId" /> <result column ="two_coursePubId" property ="coursePubId" /> </association > </collection > </resultMap > <select id ="selectTreeNodes" parameterType ="long" resultMap ="treeNodeResultMap" > select one.id one_id, one.pname one_pname, one.parentid one_parentid, one.grade one_grade, one.media_type one_mediaType, one.start_time one_startTime, one.end_time one_endTime, one.orderby one_orderby, one.course_id one_courseId, one.course_pub_id one_coursePubId, two.is_preview two_isPreview, two.id two_id, two.pname two_pname, two.parentid two_parentid, two.grade two_grade, two.media_type two_mediaType, two.start_time two_startTime, two.end_time two_endTime, two.orderby two_orderby, two.course_id two_courseId, two.course_pub_id two_coursePubId, m1.media_fileName mediaFilename, m1.id teachplanMeidaId, m1.media_id mediaId from teachplan one left join teachplan two on two.parentid = one.id left join teachplan_media m1 on two.id = m1.teachplan_id where one.parentid = 0 and one.course_id = #{id} order by one.orderby , two.orderby; </select >
MYSQL
1、导出数据库表以及所有信息的指令
1 2 3 4 5 mysqldump -u 用户名 -p 数据库名 > 导出文件路径 mysqldump -u root -p mydatabase > d:\mydatabase.sql
2、MySQl的递归查询
当我们想要查询一种递归数据。例如课程计划 。 一个计划可能包含多个子计划,子计划又包含多个子子计划。
数据库中:
我们可以这样递归查询 RECURSIVE递归的意思
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 WITH RECURSIVE t1 AS ( SELECT * FROM course_category p WHERE id = '1' UNION ALL SELECT t.* FROM course_category t INNER JOIN t1 ON t1.id = t.parentid ) SELECT * FROM t1ORDER BY t1.id, t1.orderby;
如果某一轮查询没有找到任何符合条件的记录,那么递归查询就会终止。