MyBatisPlus

主要是黑马商城的学习笔记

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("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
user.setInfo(UserInfo.of(24,"英文老师","female"));
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
userMapper.insert(user);
}

5、常用注解介绍

  1. @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
  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 指定主键类型
  1. @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. 实体类的别名扫描包

    1
    2
    3
    4
    5
    mybatis-plus:
    type-aliases-package: com.itheima.mp.domain.po
    global-config:
    db-config:
    id-type: auto # 全局id类型为自增长
  2. MyBatisPlus支持手写SQL的,mapper文件的读取地址可以自己配置

    1
    2
    mybatis-plus:
    mapper-locations: "classpath*:/mapper/**/*.xml" # Mapper.xml文件地址,当前这个是默认值。

7、条件构造器(仅供参考)

  1. QueryWrapper

    1
    2
    3
    4
    5
    6
    7
    // 1.构建查询条件 where name like "%o%" AND balance >= 1000
    QueryWrapper<User> wrapper = new QueryWrapper<User>()
    .select("id", "username", "info", "balance")
    .like("username", "o")
    .ge("balance", 1000);
    // 2.查询数据
    List<User> users = userMapper.selectList(wrapper);
  2. 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);
    // 1.生成SQL
    UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
    .setSql("balance = balance - 200") // SET balance = balance - 200
    .in("id", ids); // WHERE id in (1, 2, 4)
    // 2.更新,注意第一个参数可以给null,也就是不填更新字段和数据,
    // 而是基于UpdateWrapper中的setSQL来更新
    userMapper.update(null, wrapper);
  3. LambdaQueryWrapper

    减少硬编码

    • LambdaQueryWrapper

    • LambdaUpdateWrapper

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 1.构建条件 WHERE username LIKE "%o%" AND balance >= 1000
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.lambda()
    .select(User::getId, User::getUsername, User::getInfo, User::getBalance)
    .like(User::getUsername, "o")
    .ge(User::getBalance, 1000);
    // 2.查询
    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

  1. 复杂SQL

比如我们需要动态的构造复杂SQL

1
2
3
4
5
6
7
8
9
@Test
void testCustomWrapper() {
// 1.准备自定义查询条件
List<Long> ids = List.of(1L, 2L, 4L);
QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id", ids);

// 2.调用mapper的自定义方法,直接传递Wrapper
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);
}
  1. 多表关联

    之前我们要查询出所有收货地址在北京的并且用户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() {
    // 1.准备自定义查询条件
    QueryWrapper<User> wrapper = new QueryWrapper<User>()
    .in("u.id", List.of(1L, 2L, 4L))
    .eq("a.city", "北京");

    // 2.调用mapper的自定义方法
    List<User> users = userMapper.queryUserByWrapper(wrapper);

    users.forEach(System.out::println);
    }


    //Mapper
    @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. 用法

    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() {
// 准备10万条数据
List<User> list = new ArrayList<>(1000);
long b = System.currentTimeMillis();
for (int i = 1; i <= 100000; i++) {
list.add(buildUser(i));
// 每1000条批量插入一次
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、代码生成器

  1. 下载Idea插件MyBatisPlus

1

  1. 上方工具栏有先点击Config Database配置连接数据库。然后再点击Code Generator

    2

  2. 配置好相关信息,生成代码

    3

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 # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 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类型。

4

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类型改为枚举类型

4_2

@EnumValue注解来标记枚举属性作为数据库的值,@JsonValue注解标记JSON序列化时(前端得到的)展示的字段:

配置枚举处理器(貌似3.5.2开始不用配置)

1
2
3
mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler

预期结果,前端显示的信息是枚举的desc描述

4_3

同时呢数据库存储的则是用枚举的value存储的int类型

4_4)

14、JSON类型处理器

问题发现:

  1. 数据库中存在一个JSON字段,例如

    5

  2. 而对应存储的实体类的属性却是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类型处理器

解决思路:

  1. 替换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;
    }
  2. 在字段上声明类型处理器

    1
    2
    3
    //开启jason数据映射
    @TableField(typeHandler = JacksonTypeHandler.class)
    private UserInfo info;
  3. 之后则会自动向数据库中存JSON字符串。java代码中使用的时候则会自动将JSON转成UserInfo对象

15、分页插件

  1. 配置Mybatis配置项

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Configuration
    public class MyBatisConfig {
    //mybatis-plus配置类
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
    //mybatis-plus拦截器
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    //1、创建分页插件
    PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
    paginationInnerInterceptor.setMaxLimit(1000L);
    //2、添加分页插件
    interceptor.addInnerInterceptor(paginationInnerInterceptor);

    return interceptor;
    }
    }
  2. 使用分页API

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Test
    void testPageQuery() {
    // 1.分页查询,new Page()的两个参数分别是:页码、每页大小
    Page<User> p = userService.page(new Page<>(2, 2));
    // 2.总条数
    System.out.println("total = " + p.getTotal());
    // 3.总页数
    System.out.println("pages = " + p.getPages());
    // 4.数据
    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 {

// TODO 修改服务名以及数据表名
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[]{
// "mq_message",
// "mq_message_history"
"course_base",
"course_market",
"teachplan",
"teachplan_media",
"course_teacher",
"course_category",
"course_publish",
"course_publish_pre"

};

// TODO 默认生成entity,需要生成DTO修改此变量
// 一般情况下要先生成 DTO类 然后修改此参数再生成 PO 类。
private static final Boolean IS_DTO = false;

public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 选择 freemarker 引擎,默认 Velocity 设置模版引擎
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
// 全局配置
GlobalConfig gc = new GlobalConfig();

//生成的代码将覆盖文件
gc.setFileOverride(true);
//生成路径 getProperty("user.dir")会获取当前项目的根目录路径
gc.setOutputDir(System.getProperty("user.dir") + "/xuecheng-plus-generator/src/main/java");
gc.setAuthor("itcast");
gc.setOpen(false); //生成完毕是否自动展开输出目录
gc.setSwagger2(false); //设置是否生成 Swagger2 注解
gc.setServiceName("%sService");
gc.setBaseResultMap(true); //设置是否生成基本的 resultMap。resultMap 是 MyBatis 用来映射查询结果到 Java 对象的配置。
gc.setBaseColumnList(true); //设置是否生成基本的 columnList。columnList 是 MyBatis 用来映射表的字段列表的配置。

if (IS_DTO) { //需要生成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.jdbc.Driver");
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); //启用 Lombok 模型
strategy.setRestControllerStyle(true); //Rest风格Controller设计
strategy.setInclude(TABLE_NAMES); //设置要生成包含的数据表名
strategy.setControllerMappingHyphenStyle(true); //设置为 true 后,映射路径会使用短横线(-)分隔命名风格,类似于 RESTful 风格的路径(如 user-info)。
strategy.setTablePrefix(pc.getModuleName() + "_"); //设置生成代码时,表名前缀的去除。这里的 pc.getModuleName() 是从配置中获取的模块名称,pc 是 PackageConfig 对象,它可以设置模块相关的配置(如模块名称)。这行代码的意思是,生成代码时会去掉数据库表名的前缀,例如表 xcplus_user 会生成 User 实体类,而不是 XcplusUser。
// Boolean类型字段是否移除is前缀处理
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的递归查询

当我们想要查询一种递归数据。例如课程计划。 一个计划可能包含多个子计划,子计划又包含多个子子计划。

7

数据库中:

6

我们可以这样递归查询 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 t1
ORDER BY t1.id, t1.orderby;

如果某一轮查询没有找到任何符合条件的记录,那么递归查询就会终止。