MyBatis学习笔记二

本文最后更新于:3 个月前

本文是第二篇MyBatis的学习笔记,这次的比较深入理论,记录各个分部的功能和作用。

MyBatis


一、XML配置

1.属性(properties)

这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。例如:

1
2
3
4
5
6
7
<!-- 用于连接外部配置文件 -->
<!-- 外部配置文件路径-->
<properties resource="db.properties">
<!--同名配置优先使用外部配置文件-->
<property name="username" value="root"/>
<property name="password" value="123456"/>
</properties>

设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值。比如:

1
2
3
4
5
6
7
<!-- 有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]")-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>

2.类型别名(typeAliases)

1.类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:

1
2
3
<typeAliases>
<typeAlias alias="User" type="com.zmq.pojo.User"/>
</typeAliases>

当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。

2.也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

1
2
3
<typeAliases>
<package name="com.zmq.pojo"/>
</typeAliases>

每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子:

1
2
3
4
@Alias("author")
public class Author {
...
}

实体类比较少的时候,用第一种方式。

若实体类非常多,则建议用第二种。

第一种可以DIY别名,第二种不行。如果非要改,要在实体类添加注解

3.设置(settings)

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。

一个配置完整的 settings 元素的示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

4.映射器(mappers)

既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。例如:

方式一:【推荐使用】

1
2
3
4
5
6
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

方式二:

1
2
3
4
5
6
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>

方式三:

1
2
3
4
5
6
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

注意:

  • 接口和他的Mapper配置文件必须同名!
  • 接口和他的Mapper配置文件必须在同一个包下!

方式四:扫描包进行绑定

1
2
3
4
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>

这些配置会告诉 MyBatis 去哪里找映射文件

二、XML映射文件

1.结果映射

1.resultMap

结果集映射

1
2
id   name   pwd
id name password
1
2
3
4
5
6
7
8
9
<!--    结果集映射-->    
<resultMap id="UserMap" type="User">
<result column="id" property="id" />
<result column="name" property="name" />
<result column="pwd" property="password" />
</resultMap>
<select id="getUserById" parameterType="int" resultType="User" resultMap="UserMap">
select * from mydb.user where id = #{id}
</select>

特点:

  • resultMap 元素是 MyBatis 中最重要最强大的元素。
  • ResultMap的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂语句,描述他们的关系就可以

2.日志工厂

1.STDOUT_LOGGING标准日志

1
2
3
4
5
6
<!-- 设置 -->
<settings>
<!-- 标准日志工厂实现-->
<!-- 日志的值:SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

一次id查询的控制台打印:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Opening JDBC Connection
Created connection 786041152.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@2eda0940]
==> Preparing: select * from mydb.user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, alice, 123456
<== Total: 1
User{id=1, name='alice', password='123456'}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@2eda0940]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@2eda0940]
Returned connection 786041152 to pool.

Process finished with exit code 0

2.LOG4J

引入Log4j依赖

1
2
3
4
5
6
<!-- 加入log4j支持 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

Log4j配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
log4j.rootLogger=DEBUG,console,file

#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/zmq.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

一次id查询的控制台打印:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[org.apache.ibatis.logging.LogFactory]-Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
[org.apache.ibatis.logging.LogFactory]-Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
[org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
[org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
[org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
[org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 660879561.
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@276438c9]
[com.zmq.dao.UserDao.getUserById]-==> Preparing: select * from mydb.user where id = ?
[com.zmq.dao.UserDao.getUserById]-==> Parameters: 1(Integer)
[com.zmq.dao.UserDao.getUserById]-<== Total: 1
User{id=1, name='alice', password='123456'}
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@276438c9]
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@276438c9]
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 660879561 to pool.

简单使用

1.在要使用的log4j的类中,导入包import org.apache.log4j.Logger;

2.日志对象,参数为当前类的Class

3.日志级别

三、分页

减少数据处理量

1.使用limit分页

1
2
3
语法:
SELECT * from user limit startIndex,pagesize;
SELECT * from user limit 3;#[0,n]

使用Mybatis实现分页,核心SQL

1.接口

2.Mapper.xml

1
2
3
<select id="getUserByLimit" parameterType="map" resultType="User" resultMap="UserMap">
select * from mydb.user limit #{startIndex},#{pageSize}
</select>

3.测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//分页查询测试
@Test
public void getUserByLimit(){
//获取sqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);

HashMap<String,Integer> map=new HashMap<String, Integer>();
map.put("startIndex",1);
map.put("pageSize",2);
List<User> userList = userDao.getUserByLimit(map);
for (User user : userList){
System.out.println(user);
}


//关闭sqlSession
sqlSession.close();
}

2.RowBounds分页

不再使用SQL 实现分页

1.接口

1
2
3
//分页二
//UserDao.java
List<User> getUserByRowBounds();

2.mapper.xml

1
2
3
4
<!--    Mapper.xml-->
<select id="getUserByRowBounds" resultType="User" resultMap="UserMap">
select * from mydb.user
</select>

3.测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//测试类
@Test
public void getUserByRowBounds(){

RowBounds rowBounds = new RowBounds(1,2);
//获取sqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
List<User> userList = sqlSession.selectList("com.zmq.dao.UserDao.getUserByRowBounds",null,rowBounds);

for (User user : userList){
System.out.println(user);
}
//关闭sqlSession
sqlSession.close();
}

3.分页插件

pagehelper

官方文档

官方文档写的很详细,这里不再展开

四、注解

1.注解在接口实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@select("select * from user")
List<User> getUsers();

//有多个参数时,必须加@Param注解
@Select("Select * from user where id = #{id} and name = #{name}")
User getUserById(@Param("id") int id,@Param("name") String name);

//引用对象不需要写@Param
@Insert("insert into User{id,name,password} values(#{id},#{name},#{password})")
int addUser(User user);

//引用对象不需要写@Param
@Update("update User set name=#{name},pwd=#{passwoord} where id=#{id}")
int UpdateUser(User user);

//删除
@Delete("delete * from user where id=#{id}")
int delUser(int id);

2.核心配置文件绑定接口

1
2
3
<mappers>
<mapper class="com.zmq.dao.UserDao"></mapper>
</mappers>

3.测试

本质:反射机制实现

底层:动态代理

对于像 BlogMapper 这样的映射器类来说,还有另一种方法来完成语句映射。

它们映射的语句可以不用 XML 来配置,而可以使用 Java 注解来配置。比如,上面的 XML 示例可以被替换成如下的配置:

1
2
3
4
5
package org.mybatis.example;
public interface BlogMapper {
@Select("SELECT * FROM blog WHERE id = #{id}")
Blog selectBlog(int id);
}

​ 使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

​ 选择何种方式来配置映射,以及认为是否应该要统一映射语句定义的形式,完全取决于你和你的团队。 换句话说,永远不要拘泥于一种方式,你可以很轻松的在基于注解和 XML 的语句映射方式间自由移植和切换。

4.CRUD

注:查询是不需要提交事务的!涉及数据库变动的才需要!

我们可以在工具类的时候实现自动提交事务!

但是开发测试时,推荐设置为非自动提交,可以防止提交错误的东西进数据库。

1
2
3
//SqlSession 完全包含了面向数据库执行SQL命令的所有方法public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}

测试类:

此处省略。

方法如4.1代码所示。

【注意:我们必须要将接口注册绑定到我们的核心配置文件中!】

关于@Param()注解:
  • 基本类型的参数是String类型,需要加上
  • 引用类型不需要加
  • 如果只有一个基本类型的话,可以忽略,但是建议大家都加上!
  • 我们在SQL中引用的就是我们这里的@Param()中设定的属性名 !
这里说一下#{}和${}的区别:
  • #{}是预编译处理,${}是字符串替换。
  • Mybatis 在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 的 set 方法来赋值;
  • Mybatis 在处理时,就是把{}时,就是把{}替换成变量的值。
  • 使用#{}可以有效的防止 SQL 注入,提高系统安全性。

Mybatis排序时使用order by动态参数时,使用${},而不是#{}

5.Lombok

  • 是一个Java库
  • 也是个插件
  • 也是个工具
  • 再也不用写get,set等方法了,只需要在类上加注解就可以
  • 一个可以偷懒的,解决你手动写get和set方法的第三方插件

使用步骤:

  1. 在IDEA中安装Lombok插件!

  2. 导入jar包(去Maven仓库搜索

    1
    2
    3
    4
    5
    6
    7
    8
    <!-- 这是我写笔记的时候的最新版,追求稳定就用使用人数多的 -->
    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.22</version>
    <scope>provided</scope>
    </dependency>
  3. 在实体类上加注解即可

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package com.zmq.pojo;

    import lombok.Data;
    import lombok.NoArgsConstructor;
    import lombok.AllArgsConstructor;

    //无参构造,get和set,tostring,equals和hashcode方法
    @Data
    //所有构造方法
    @AllArgsConstructor
    //构建了有参,无参会隐藏,必须显式无参构造
    @NoArgsConstructor
    public class User {
    private int id;
    private String name;
    private String password;
    }
  4. Lombok可以使用的注解:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    @Getter and @Setter  ----->Get和Set方法,可以放在类上(构建所有),也可以放在字段上(指定单个构建)
    @FieldNameConstants
    @ToString ----->ToString方法
    @EqualsAndHashCode
    @AllArgsConstructor ----->所有构造方法
    @RequiredArgsConstructor ----->有参构造方法
    @NoArgsConstructor ----->无参构造方法

    @Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
    @Data ----->无参构造,get和set,tostring,equals和hashcode方法
    @Builder
    @SuperBuilder
    @Singular
    @Delegate
    @Value
    @Accessors
    @Wither
    @With
    @SneakyThrows
    @val
    @var
    experimental @var
    @UtilityClass

    注:@RequiredArgsConstructor在类上使用,这个注解可以生成带参或者不带参的构造方法。
    若带参数,只能是类中所有带有@NonNull注解的和以final修饰的未经初始化的字段,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package com.zmq.pojo;

    import lombok.*;

    @RequiredArgsConstructor
    @NoArgsConstructor
    public class User {
    @NonNull
    private int id;
    //但这里爆红未初始化变量
    private final String name;
    private String password;
    }

    所以平常还是直接这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package com.zmq.pojo;

    import lombok.Data;
    import lombok.NoArgsConstructor;
    import lombok.AllArgsConstructor;

    //无参构造,get和set,tostring,equals和hashcode方法
    @Data
    //所有构造方法
    @AllArgsConstructor
    //构建了有参,无参会隐藏,必须显式无参构造
    //有参构造如果要重载的话,还是要自己写代码的
    @NoArgsConstructor
    public class User {
    private int id;
    private String name;
    private String password;
    }

    注:此工具看工作环境,用不用看自己,适合开发的才是王道。

五、关系映射

1.多对一关系

多对一:多个学生对应一个老师----->关联

一对多:一个老师教一群学生--------->集合


注意:

​ 多表关联一般会用到外键,外键本身是为了实现强一致性,所以如果需要正确性>性能的话,还是建议使用外键,它可以让我们在数据库的层面保证数据的完整性和一致性。

​ 当然不用外键,你也可以在业务层进行实现。不过,这样做也同样存在一定的风险,因为这样,就会让业务逻辑会与数据具备一定的耦合性。也就是业务逻辑和数据必须同时修改。而且在工作中,业务层可能会经常发生变化。

​ 当然,很多互联网的公司,尤其是超大型的数据应用场景,大量的插入,更新和删除在外键的约束下会降低性能,同时数据库在水平拆分和分库的情况下,数据库端也做不到执行外键约束。另外,在高并发的情况下,外键的存在也会造成额外的开销。因为每次更新数据,都需要检查另外一张表的数据,也容易造成死锁。
所以在这种情况下,尤其是大型项目中后期,可以采用业务层来实现,取消外键提高效率。

​ 不过在SQL学习之初,包括在系统最初设计的时候,还是建议你采用规范的数据库设计,也就是采用外键来对数据表进行约束。因为这样可以建立一个强一致性,可靠性高的数据库结构,也不需要在业务层来实现过多的检查。
​ 当然在项目后期,业务量增大的情况下,你需要更多考虑到数据库性能问题,可以取消外键的约束,转移到业务层来实现。而且在大型互联网项目中,考虑到分库分表的情况,也会降低外键的使用。

​ 不过在SQL学习,以及项目早期,还是建议你使用外键。在项目后期,你可以分析有哪些外键造成了过多的性能消耗。一般遵循2/8原则,会有20%的外键造成80%的资源效率,你可以只把这20%的外键进行开放,采用业务层逻辑来进行实现,当然你需要保证业务层的实现没有错误。不同阶段,考虑的问题不同。当用户和业务量增大的时候,对于大型互联网应用,也会通过减少外键的使用,来减低死锁发生的概率,提高并发处理能力。


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
/* 多对一训练数据库准备 */
/* 数据库首次执行 */
USE mydb

/* 数据库第二次执行 */
/* 创建teacher表 */
CREATE TABLE `teacher`(
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;

/* 数据库第三次执行 */
/* 往teacher表插入数据 */
INSERT INTO teacher(`id`,`name`) VALUES (1,'艾莎');

/* 数据库第四次执行 */
/* 创建student表 */
CREATE TABLE `student`(
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8

/* 数据库第五次执行 */
/* 往Student表插入数据 */
INSERT INTO student(`id`,`name`,`tid`) VALUES (1,'张三',1);
INSERT INTO student(`id`,`name`,`tid`) VALUES (2,'李四',1);
INSERT INTO student(`id`,`name`,`tid`) VALUES (3,'王五',1);
INSERT INTO student(`id`,`name`,`tid`) VALUES (4,'赵六',1);
INSERT INTO student(`id`,`name`,`tid`) VALUES (5,'孙七',1);

(1)测试环境搭建

1.导入lombok(可选,自己喜欢就用)

2.新建实体类 Teacher,Student

3.建立Mapper 接口

4.建立Mapper.xml文件

5.在核心配置文件中绑定注册我们的Mapper接口或文件!【方式很多,随心选】

6.测试查询是否能成功!

(2)按照查询嵌套处理

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
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace用来绑定DAO接口-->
<mapper namespace="com.zmq.dao.StudentMapper">

<!--
思路:
1.查询所有学生信息
2.根据查询出来的学生的id,寻找对应的老师
-->
<select id="getStudent" resultMap="StudentTeacher">
select * from student;
</select>

<resultMap id="StudentTeacher" type="Student">
<result property="id" column="id" />
<result property="name" column="name" />
<!-- 复杂的属性,我们需要单独处理 对象: association 集合:collection -->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacherById"/>
</resultMap>

<select id="getTeacherById" resultType="Teacher">
select * from teacher where id = #{id};
</select>

</mapper>

(3)按照结果嵌套处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--    按照结果嵌套处理-->
<select id="getStudent2" resultMap="StudentTeacher2">
select s.id sid,s.name sname,t.name tname
from student s,teacher t
where s.tid = t.id
</select>

<resultMap id="StudentTeacher2" type="Student">
<result property="id" column="sid" />
<result property="name" column="sname" />
<association property="teacher" javaType="Teacher">
<result property="name" column="tname" />
</association>
</resultMap>

2.一对多处理

一个老师拥有多个学生---------->对于老师来说,就是一对多的关系!

(1)测试环境搭建

实体类

1
2
3
4
5
6
7
@Data
public class Teacher {
private int id;
private String name;
//一个老师拥有多个学生
private List<Student> student;
}
1
2
3
4
5
6
@Data
public class Student {
private int id;
private String name;
private int tid;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.zmq.dao;

import com.zmq.pojo.Teacher;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface TeacherMapper {

//获取指定老师的学生和老师的信息
Teacher getTeacher2(@Param("tid") int id);

Teacher getTeacher3(@Param("tid") int id);
}

(2)按照查询嵌套处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--    按结果嵌套查询-->
<select id="getTeacher2" resultMap="TeacherStudent">
select s.id sid,s.name sname,t.name tname,t.id tid
from student s,teacher t
where s.tid = t.id and t.id = #{tid}
</select>

<resultMap id="TeacherStudent" type="Teacher">
<result property="id" column="tid" />
<result property="name" column="tname" />
<!-- javatype指定属性的类型-->
<collection property="student" ofType="Student">
<result property="id" column="sid" />
<result property="name" column="sname" />
<result property="tid" column="tid" />
</collection>

</resultMap>

(3)按照结果嵌套处理

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 按照查询嵌套处理 -->
<select id="getTeacher3" resultMap="TeacherStudent2">
select * from teacher where id = #{tid}
</select>
<!-- ofType泛型 -->
<resultMap id="TeacherStudent2" type="Teacher">
<collection property="student" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId" column="id"/>
</resultMap>

<select id="getStudentByTeacherId" resultType="Student">
select * from student where tid = #{tid}
</select>

小结

  1. 关联 - association 【多对一】
  2. 集合 - collection 【一对多】
  3. JavaType & oftype
    1. javaType 用来指定实体类中属性的类型
    2. ofType 用来指定映射到List或者集合中的pojo类型,泛型中的约束类型。

注意点:

  • 保证SQL的可读性,尽量保证通俗易懂
  • 注意一对多和多对一中,属性名和字段的问题!
  • 如果问题不好排查错误,可以使用日志,建议使用Log4j

六、动态SQL

简单讲,动态SQL就是指根据不同的条件生成不同的SQL语句。

1. 搭建环境

1
2
3
4
5
6
7
CREATE TABLE `blog`(
`id` varchar(50) NOT NULL COMMENT '博客id',
`title` varchar(100) NOT NULL COMMENT '博客标题',
`author` varchar(50) NOT NULL COMMENT '博客作者',
`create_time` datetime NOT NULL COMMENT '创建时间',
`views` int(30) NOT NULL COMMENT '浏览量'
)ENGINE=InnoDB DEFAULT CHARSET=utf8

2. 创建一个基础工程

  1. 导包

  2. 编写配置文件

  3. 编写实体类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package com.zmq.pojo;

    import lombok.Data;

    import java.util.Date;

    @Data
    public class Blog {
    private int id;
    private String title;
    private String author;
    private Date createTime;
    private int views;

    }
  4. 编写实体类对应的Mapper接口和Mapper.xml文件

1
2
3
4
5
6
7
8
9
10
11
package com.zmq.dao;

import com.zmq.pojo.Blog;

public interface BlogMapper {

int addBlog(Blog blog);

//SQL动态查询IF
List<Blog> queryBlogIF(Map map);
}
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
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace用来绑定DAO接口-->
<mapper namespace="com.zmq.dao.BlogMapper">

<insert id="addBlog" parameterType="Blog">
insert into blog(id,title,author,create_time,views)
values (#{id},#{title},#{author},#{createTime},#{views});
</insert>

<!-- 动态SQL查询 -->
<select id="queryBlogIF" parameterType="Map" resultType="Blog">
<!-- where 1=1 必定能查出东西,就是参数为空的时候查所有数据 -->
select * from blog where 1=1
<!-- 如果title不为空,则进行SQL拼接 -->
<if test="title != null">
and title = #{title}
</if>
<!-- 如果author不为空,则进行SQL拼接 -->
<if test="author != null">
and author = #{author}
</if>
</select>

</mapper>

测试类

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
import com.zmq.dao.BlogMapper;
import com.zmq.pojo.Blog;
import com.zmq.utils.IDUtils;
import com.zmq.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class mytest {

@Test
public void test() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Blog blog = new Blog();
blog.setId(IDUtils.getId());
blog.setTitle("Java学习笔记");
blog.setAuthor("星空");
blog.setCreateTime(new Date());
blog.setViews(233);

mapper.addBlog(blog);

blog.setId(IDUtils.getId());
blog.setTitle("Java学习笔记2");
mapper.addBlog(blog);

blog.setId(IDUtils.getId());
blog.setTitle("Java学习笔记3");
mapper.addBlog(blog);

blog.setId(IDUtils.getId());
blog.setTitle("Java学习笔记4");
mapper.addBlog(blog);

sqlSession.commit();
sqlSession.close();
}

@Test
public void test1() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map map = new HashMap();
map.put("author","幻境");
map.put("title","Mybatis学习笔记");
//这里的map空值也能查出东西,不是空值就会判断进行SQL拼接
List<Blog> blogs = mapper.queryBlogIF(map);
for (Blog blog : blogs) {
System.out.println(blog);
}

sqlSession.close();
}
}

3.元素种类

(1)IF

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--    动态SQL查询  -->
<select id="queryBlogIF" parameterType="Map" resultType="Blog">
<!-- where 1=1 必定能查出东西,就是参数为空的时候查所有数据 -->
select * from blog where 1=1
<!-- 如果title不为空,则进行SQL拼接 -->
<if test="title != null">
and title = #{title}
</if>
<!-- 如果author不为空,则进行SQL拼接 -->
<if test="author != null">
and author = #{author}
</if>
</select>

(2)Choose(when,otherwise)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<select id="queryBlogChoose" parameterType="Map" resultType="Blog">
select * from blog
<where>
<choose>
<when test="title != null">
and title = #{title}
</when>
<when test="author != null">
and author = #{author}
</when>
<otherwise>
and views = #{views}
</otherwise>
</choose>
</where>

</select>

(3)trim(where,set)

1
2
3
4
5
6
7
8
9
10
11
12
 <!--  解决Where查询只有第二个有参数时直接拼接and的错误问题 -->
<select id="queryBlogIF" parameterType="Map" resultType="Blog">
select * from blog
<where>
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>

注:where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除

SQL片段

有的时候,我们可能会将一些功能的部分抽取出来,方便复用!

1.使用SQL标签抽取公共的部分

1
2
3
4
5
6
7
8
<sql id="if-title-author">
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>

2.在需要的地方使用include标签引用即可

1
2
3
4
5
6
<select id="queryBlogIF" parameterType="Map" resultType="Blog">
select * from blog
<where>
<include refid="if-title-author"></include>
</where>
</select>

注意:

  • 最好基于单表来定义SQL片段。(适合简单的查询复用)
  • 不要存在where标签

(4)Forsech

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--  
select * from blog where 1=1 and (id =1 or id=2 or id = 3)

我们现在传递一个万能的map,在map中可以存在一个集合!
-->
<select id="queyBlogForeach" parameterType="map" resultType="Blog">
select * from blog
<where>
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id = #{id}
</foreach>
</where>
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//测试类
@Test
public void test4() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map map = new HashMap();
ArrayList<Integer> ids = new ArrayList<Integer>();
ids.add(1);
ids.add(2);
ids.add(3);
map.put("ids",ids);

List<Blog> blogList = mapper.queyBlogForeach(map);

for (Blog blog : blogList) {
System.out.println(blog);
}

sqlSession.close();
}

所谓的动态SQL,本质还是SQL语句,只是我们可以在SQL层面,去执行一个逻辑代码。

动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的格式去排列组合就好了。

建议:

先在MySQL中写出完整的SQL再去对应的去修改成为我们的动态SQL实现通用即可!

七、缓存(了解)

1.简介

查询:连接数据库,耗资源!

一次查询的结果,给他暂存在一个可以直接取到的地方!–>内存:缓存

我们再次查询相同数据的时候,直接走缓存,就不用走数据库了。

(1) 什么是缓存【Cache】?

存在内存中的临时数据。

将用户经常查询的数据放在缓存中,用户去查询数据就不用从磁盘上直接查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

(2) 为什么使用缓存?

减少和数据库的交互次数,减小系统开销,提高系统效率。

(3) 什么样的数据能使用缓存?

经常查询并且不经常改变的数据(经常读取而不是写入的)

2.MyBatis缓存

MyBatis包含一个非常强大的查询缓存特性,他可以非常方便地定制和配置缓存。缓存可以极大地提升查询效率。

MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存

  • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
  • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
  • 为了提高扩展性,MyBatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存

3.一级缓存

一级缓存也叫本地缓存:SqlSession

与数据库同一次会话期间查询到的数据会放在本地缓存中。

以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//测试
@Test
public void getUserLike(){
//获取sqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//sqlSession.getMapper(UserDao.class);后ALT+回车生成赋给的对象UserDao
//这里要拿SQL,从Dao包里拿,面向接口编程,可以直接从UserDao接口里拿
UserDao userDao = sqlSession.getMapper(UserDao.class);
//执行方法
List<User> userList = userDao.getUserLike("张");

//增强For循环输出
for(User user:userList){
System.out.println(user);
}
List<User> user = userDao.getUserLike("张");

for (User user1 : user) {
System.out.println(user1);
}

//关闭sqlSession
sqlSession.close();
}

控制台显示:

1
2
3
4
5
6
7
8
9
10
11
12
13
Opening JDBC Connection
Created connection 1075738627.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@401e7803]
==> Preparing: select * from mydb.user where name like "%"?"%"
==> Parameters: 张(String)
<== Columns: id, name, pwd
<== Row: 4, 张三, 123456
<== Total: 1
User{id=4, name='张三', pwd='123456'}
User{id=4, name='张三', pwd='123456'}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@401e7803]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@401e7803]
Returned connection 1075738627 to pool.

缓存失效的情况:

4.二级缓存

  1. 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
  2. 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
  3. 工作机制:
  • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
  • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存在二级缓存中;
  • 新的会话查询信息,就可以从二级缓存中获取内容;
  • 不同的mapper查出的数据会放在自己对应的缓存(map)中;

步骤:

  1. 开启全局缓存

    1
    2
    <!-- 显式的开启全局缓存  -->
    <setting name="cacheEnabled" value="true"/>
  2. 在要使用二级缓存的Mapper中开启

    1
    2
    <!--  在当前Mapper.xml中使用二级缓存   -->
    <cache />

也可以自定义参数

1
2
3
4
5
<!--  在当前Mapper.xml中使用二级缓存   -->
<cache eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true" />

这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

可用的清除策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

默认的清除策略是 LRU。

flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。

提示 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。

  1. 测试

1.问题:我们需要将实体类序列化!否则就会报错!

1
Caused by: java.io.NotSerializableException: com.zmq.pojo.Blog

解决:实现序列化接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.zmq.pojo;

import lombok.Data;

import java.io.Serializable;
import java.util.Date;

@Data
public class Blog implements Serializable {
private String id;
private String title;
private String author;
private Date createTime;
private int views;

}

或者加上readOnly=“true”

1
<cache  readOnly="true" />

注:中的readOnly默认为false,而可读写的缓存会通过序列化返回缓存对象的拷贝,此时需要实体类(这里是Blog)实现Serializable接口或者配置readOnly=true

小结:

  • 只要开启了二级缓存,在同一个Mapper下就有效
  • 所有的数据都会先放在一级缓存中;
  • 只有当会话提交,或者关闭的时候,才会提交到二级缓存中!

5.缓存原理

SqlSession请求—>数据库----->一级缓存------二级缓存

用户查询先走二级缓存,二级缓存没有再走一级缓存,再没有就去数据库找结果。

6.自定义缓存----->以ehcache为例

Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,

要在程序中使用ehcache,先要导包!

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.1.0</version>
</dependency>

在Mapper中指定使用ehcache缓存实现!

1
<cache type="org.mybatis.caches.ehcache.EhcacheCache" />

配置文件 —> ehcache.xml

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
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">

<diskStore path="./tmpdir/Tmp_EhCache"/>

<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>

<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>

公司里一般用Redis数据库来做缓存。


MyBatis学习笔记二
https://superlovelace.top/2023/10/25/MyBatis_2/
作者
棱境
发布于
2023年10月25日
更新于
2023年10月31日
许可协议