MyBatis学习笔记

文章目录

    • 第一个MyBatis程序
      • 1. 导入依赖
      • 2. 编写核心配置文件
      • 3. 编写工具类连接数据库
      • 4. pojo对象
      • 5. 编写Mapper接口
      • 6. 测试
    • 增删改操作
      • 添加数据
      • 删除数据
      • 修改数据
    • 利用Map传参
    • 环境配置
      • 类型别名
      • 映射器
    • ResultMap结果集映射
    • 日志
      • STDOUT_LOGGING
      • Log4j
    • 分页
    • 自动提交事务
    • 注解的应用
      • 增删改查(CRUD)
      • @Param注解
    • 多对一处理方法
    • 一对多的处理方法
    • 动态SQL
      • if语句
      • choose(when, otherwise)语句
      • set
      • sql代码片段
      • foreach语句
    • 缓存
      • 一级缓存
      • 二级缓存

第一个MyBatis程序

项目目录结构
MyBatis学习笔记_第1张图片

创建一个空Maven项目

1. 导入依赖

依赖在pom.xml导入


<dependencies>
    
    <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
        <version>8.0.19version>
    dependency>
    
    <dependency>
        <groupId>org.mybatisgroupId>
        <artifactId>mybatisartifactId>
        <version>3.5.4version>
    dependency>
    
    <dependency>
        <groupId>junitgroupId>
        <artifactId>junitartifactId>
        <version>4.13version>
    dependency>

dependencies>

2. 编写核心配置文件

src/main/resource中创建mybatis-config.xml文件




<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            dataSource>
        environment>
    environments>

configuration>

与MySQL5.x不同的是,MySQL8.x的驱动是com.mysql.cj.jdbc.Driver

url中需要设置是否开启SSL连接: useSSL=true/设置编码: useUnicode=true&characterEncoding=utf8

并且,MySQL8.x必须设置时区serverTimezone=Asia/Shanghai

在这里&需要转义成&

3. 编写工具类连接数据库

public class MyBatisUtil {
    private static SqlSessionFactory sqlSessionFactory = null;

    //获取SqlSessionFactory对象
    static{
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //获取SqlSession实例
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}

SqlSession中包含了面向数据库执行的SQL命令的所有方法

4. pojo对象

pero.fisher.pojo中创建一个与表相关的类

public class User {
    private int id;
    private String name;
    private String passwd;

    public User() {
    }

    public User(int id, String name, String passwd) {
        this.id = id;
        this.name = name;
        this.passwd = passwd;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getPasswd() {
        return passwd;
    }

    public void setId(int id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setPasswd(String passwd) {
        this.passwd = passwd;
    }
}

与User类对应的表结构
MyBatis学习笔记_第2张图片

5. 编写Mapper接口

pero.fisher.dao

创建UserMapper接口

public interface UserMapper {
    List<User> getUserList();
}

创建UserMapper.xml用来实现UserMapper接口





<mapper namespace="pero.fisher.dao.UserDao">
    
    
    
    
    <select id="getUserList" resultType="pero.fisher.pojo.User">
        select * from mybatis.user
    select>
mapper>

6. 测试

test.java.pero.fisher.dao中编写测试类UserDaoTest

public class UserDaoTest {
    @Test
    public void test(){

        //获取SqlSession对象
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        //执行SQL
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        List<User> userList = userDao.getUserList();

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

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

如果你遇到下面这个错误

org.apache.ibatis.binding.BindingException: Type interface pero.fisher.dao.UserDao is not known to the MapperRegistry.

这个错误是绑定异常

每个Mapper.xml文件都需要在MyBatis核心配置文件注册

解决办法

在核心配置文件mybatis-config.xml中加入

<mappers>
    <mapper resource="pero/fisher/dao/UserMapper.xml" />
mappers>

如果你遇到下面这个错误

java.lang.ExceptionInInitializerError

### The error may exist in pero/fisher/dao/UserMapper.xml

这是一个文件过滤问题

原因是Maven默认的配置文件是存放在resource中,当前Mapper.xml是放在java中,此时Maven无法找到这个文件

解决办法

在工程配置文件pom.xml中添加

<build>
    <resources>
        <resource>
            <directory>src/main/resourcesdirectory>
            <includes>
                <include>**/*.propertiesinclude>
                <include>**/*.xmlinclude>
            includes>
            <filtering>truefiltering>
        resource>
        <resource>
            <directory>src/main/javadirectory>
            <includes>
                <include>**/*.propertiesinclude>
                <include>**/*.xmlinclude>
            includes>
            <filtering>truefiltering>
        resource>
    resources>
build>

这样可以打开java下的文件过滤

增删改操作

增删改操作需要提交事务

添加数据

UserMapper.java

//添加一行数据
int addUser(User user);

UserMapper.xml


<insert id="addUser" parameterType="pero.fisher.pojo.User" >
    insert into mybatis.user (id, name, passwd) values(#{id}, #{name}, #{passwd})
insert>

id: 方法名

parameterType: 参数类型,如果是对象时,需要写权限定名

参数如果是对象,对象中的属性可以直接取出来

UerMapperTest.java

@Test
    public void assUserTest(){
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        UserMapper userDao = sqlSession.getMapper(UserMapper.class);

        int ret = userDao.addUser(new User(4, "找流", "678"));
        System.out.println(ret);

        //提交事务
        sqlSession.commit();

        sqlSession.close();
    }

增删改操作需要提交事务

SqlSession使用完需要.close()关闭

删除数据

UserMapper.java

//删除一行数据
int delUser(int id);

UserMapper.xml

<delete id="delUser" parameterType="int">
    delete from mybatis.user where id = #{id}
delete>

修改数据

UserMapper.java

//修改数据
int update(User user);

UserMapper.xml

<update id="update" parameterType="pero.fisher.pojo.User">
    update mybatis.user set name = #{name}, passwd = #{passwd} where id = #{id}
update>

利用Map传参

利用Map传参可以让传递参数变得更加灵活,也可以应用Map传递多个参数

UserMapper.java

int update2(Map<String, Object> map);

Map的类型为

UserMapper.xml

<update id="update2" parameterType="map">
    update mybatis.user set name = #{username} where id = #{id}
update>

Map传参不会像传递pojo类一样严格要求#{}中的参数名与类中相同.

也不必在调用方法时构建一个类,特别是当需要的参数很少,而类中的参数很多,构建类的过程会显得十分臃肿和麻烦.

UserMapperTest.java

@Test
public void update2Test() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Map<String, Object> map = new HashMap<String, Object>();
    map.put("username", "flash");
    map.put("id", 1);

    int ret = mapper.update2(map);
    System.out.println(ret);

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

在调用方法时使用put()为Map传值

环境配置

MyBatis默认事务管理器是JDBC,默认连接池(POOLED)

使用配置文件配置环境

在resources目录中创建db.properties文件

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306?\
  useSSL=false&useUnicode=TRUE&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username=root
password=root

mybatis-config.xml中引入配置文件




<configuration>


    <properties resource="db.properties" />

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            dataSource>
        environment>
    environments>

    <mappers>
        <mapper resource="pero/fisher/dao/UserMapper.xml" />
    mappers>

configuration>

类型别名

为Java类型设置一个短的名字,用来减少完全限定名的冗余

mybatis-congif.xml

可以自定义类型别名

<typeAliases>
    <typeAlias type="pero.fisher.pojo.User" alias="User"/>
typeAliases>

也可以指定包名,MyBatis会使用 Bean 的首字母小写的非限定类名来作为它的别名

<typeAliases>
    <package name="pero.fisher.pojo"/>
typeAliases>

指定包名也可以自定义别名,当类有@Alias("name")时,会使用注解值作为别名

@Alias("author")
public class Author {
    ...
}

映射器

告诉 MyBatis 到哪里去找到SQL语句

1. 使用相对于类路径的资源引用

<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
mappers>

这是最常用的方法

2. 使用映射器接口实现类的完全限定类名

<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
mappers>

这种方法有两个要求

  1. 接口与它的SQL文件必须同名
  2. 接口与它的SQL文件必须在同一个包下

3. 将包内的映射器接口实现全部注册为映射器

<mappers>
  <package name="org.mybatis.builder"/>
mappers>

这种方法的要求与第二种方法相同

ResultMap结果集映射

运用ResultType访问数据库,数据库字段名必须与ResultType类中的属性名一一对应

当属性名与字段名不同时可以通过结果集映射,映射字段名对应的方法名

<resultMap id="UserMap" type="user">
    <result column="id" property="id"/>
    <result column="passwd" property="pass"/>
resultMap>
<select id="getUserById" parameterType="int" resultMap="UserMap">
    select * from mybatis.user where id = #{id}
select>

将resultType修改成resultMap,然后定义resultMap

在resultMap中只需要映射不同的部分,属性名与字段名相同的可以自动映射

日志

STDOUT_LOGGING

STDOUT_LOGGING为默认日志工厂

mybatis-config.xml中添加

<settings>
    
    <setting name="logImpl" value="STDOUT_LOGGING"/>
settings>

设置日志工厂之后,在调用Mapper方法时会显示出log

Log4j

  • log4j能够控制输出的目的地
  • 能够控制输出格式
  • 可以定义每条日志信息的级别
  • 通过一个配置文件配置,不需要编写代码

log4j的使用

  1. pom.xml中导入log4j包

<dependency>
    <groupId>log4jgroupId>
    <artifactId>log4jartifactId>
    <version>1.2.17version>
dependency>
  1. resources中编写log4j配置文件log4j.properties
### set log levels ###
# 日志级别为DEBUG输出在Console和File
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=[%d{yy/MM/dd HH:mm:ss:SSS}]-%l:%m%n

### 输出到日志文件 ###
log4j.appender.File=org.apache.log4j.RollingFileAppender
log4j.appender.File.File=./log/app.log
log4j.appender.File.MaxFileSize=10MB
log4j.appender.File.Threshold=ALL
log4j.appender.File.layout=org.apache.log4j.PatternLayout
log4j.appender.File.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH\:mm\:ss,SSS}][%c]%m%n

# 日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.mysql=DEBUG
log4j.logger.java.mysql.Statement=DEBUG
log4j.logger.java.mysql.ResultSet=DEBUG
log4j.logger.java.mysql.PreparedStatement=DEBUG
  1. 设置,在mybatis-conifg.xml
<settings>
    <setting name="logImpl" value="log4j"/>
settings>
  1. log4j使用
@Test
public void mytest() {
    log.info("普通信息");
    log.debug("debug信息");
    log.error("错误信息");
}

分页

SQL语句中使用limit分页

select * from user limit startIndex, pageSize;

MyBatis实现分页

UserMapper.java

//分页查询
List<User> getUserByLimit(Map<String, Object> map);

UserMapper.xml

<resultMap id="UserMap" type="user">
    <result column="passwd" property="pass"/>
resultMap>

<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
    select * from mybatis.user limit #{startIndex}, #{pageSize}
select>

由于Bean中的属性名与数据库字段名不一致,因此这里应用了resultMap进行结果集映射

UserMapperTest.java

@Test
public void getUserByLimitTest() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Map<String, Object> map = new HashMap<String, Object>();
    map.put("startIndex", 0);
    map.put("pageSize", 2);
    List<User> userByLimit = mapper.getUserByLimit(map);
    for (User user : userByLimit) {
        System.out.println(user);
    }

    sqlSession.close();
}

自动提交事务

SqlSessionFactory.openSession()方法中存在一个boolean类型参数boolean autoCommit,默认为false,当传递参数为true时,创建的SqlSession可以自动提交事务.

注解的应用

增删改查(CRUD)

在Mapper中可以通过注解@Select("")/@Insert("")/@Update("")/@Delete("")直接写实现,而不需要单独写一个Mapper.xml

//根据id查询数据
@Select("select * from mybatis.user where id = #{uid}")
User getUserById(@Param("uid") int id);

@Param注解

@Param注解可以标注某个参数在sql里面的名字.

在Mapper接口需要传递多个参数时,必须要使用@Param注解

传递一个基本类型的参数时也建议使用@Param注解

上面的实例代码也使用了@Param注解

@Select("select * from mybatis.user where id = #{uid}")
User getUserById(@Param("uid") int id);

多对一处理方法

假如有一个学生和老师两张表,学生与老师是多对一的关系,即学生中有一个外键指向老师,表结构如下:
MyBatis学习笔记_第3张图片
需求是要查询出学生的信息,包括学生对应老师的信息

重点是pojo类中需要得到一个类
MyBatis学习笔记_第4张图片
方式一: 按照查询嵌套处理

在Mapper中写一个接口

//查询所有学生及对应老师的信息
List<Student> getStudent();

Mapper.xml的实现


<select id="getStudent" resultMap="StudentTeacher">
    select * from mybatis.student
select>

<resultMap id="StudentTeacher" type="student">
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    
    <association property="teacher" column="tid" javaType="teacher" select="getTeacher"/>
resultMap>

<select id="getTeacher" resultType="teacher">
    select * from mybatis.teacher where id = #{id}
select>

这是一种子查询的方法

当查询学生时,只能查询到tid,而不是tid对应的Teacher类,因此需要通过resultMap将数据库中的tid字段转换成Mapper中的Teacher类

方法二: 按照结果嵌套处理

<select id="getStudent" resultMap="StudentTeacher">
    select s.id sid, s.name sname, t.name tname
    from mybatis.teacher t, mybatis.student s
    where s.tid = t.id
select>

<resultMap id="StudentTeacher" type="student">
    <result property="id" column="sid"/>
    <result property="name" column="sname"/>
    <association property="teacher" javaType="teacher">
        <result property="name" column="tname"/>
    association>
resultMap>

这是一种连表查询的方法

直接通过sql语句查询所有信息

由于Student有一个参数是Teacher,查询出来的数据需要通过resultMap指定

一对多的处理方法

一对多的需求是在pojo类中需要得到一个集合而并非一个类
MyBatis学习笔记_第5张图片
一对多与多对一的方法差类似

<select id="getTeacher" resultMap="TeacherStudent">
    select t.id tid, t.name tname, s.name sname
    from mybatis.teacher t, mybatis.student s
    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"/>
    
    <collection property="students" ofType="student">
        <result property="name" column="sname"/>
    collection>
resultMap>

有几处不同的是

  1. 集合需要用标签而不是
  2. 集合的类型要用ofType而不是javaType

动态SQL

根据不同的条件生成不同的SQL语句

if语句

<select id="getBlogsIf" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
        <if test="title != null">
            title = #{title}
        if>

        <if test="author != null">
            and author = #{author}
        if>

    where>
select>

标签可以智能判断如果语句中不需要and会自动将and去掉

choose(when, otherwise)语句

相当于switch语句

<select id="getBlogsChoose" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
        <choose>
            <when test="title != null">
                title = #{title}
            when>
            <when test="author != null">
                and author = #{author}
            when>
            <when test="views != null">
                and views = #{views}
            when>
            <otherwise>
                and views = 1000
            otherwise>
        choose>
    where>
select>

标签相当于switch语句, 当一个条件满足时,choose会终止, 不会执行下一个条件

set

<update id="updateBlog" parameterType="map">
    update mybatis.blog
    <set>
        <if test="title != null">
            title = #{title},
        if>
        <if test="author != null">
            author = #{author}
        if>
    set>
    where id = #{id}
update>

标签与标签有相似的功能,就是智能去除SQL拼接时多余的,

sql代码片段

用sql代码片段实现代码服用

<sql id="if-title-author">
    <if test="title != null">
        title = #{title},
    if>
    <if test="author != null">
        author = #{author}
    if>
sql>

<update id="updateBlog" parameterType="map">
    update mybatis.blog
    <set>
        <include refid="if-title-author">include>
    set>
    where id = #{id}
update>

通过写sql片段,通过引用sql片段

foreach语句

例如select * from mybatis.blog where (id = 1 or id = 2 or id = 3)

这样的语句, 需要获取一串元素, 就可以用实现

<select id="getBlogForeach" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
        <foreach collection="ids" item="id" open="(" close=")" separator="or">
            id = #{id}
        foreach>
    where>
select>

collection: 遍历的

item: 遍历出来的元素

open: 开始字符

close: 结束字符

separator: 分割字符

缓存

查询数据时,频繁连接数据库十分耗资源,

因此引入缓存机制,将查询的结果放入缓存中,当再次查询相同数据时,直接查缓存

  • 缓存解决了高并发系统的性能问题
  • 缓存的作用: 减少与数据库的交互次数, 减少系统开销, 提高系统效率
  • 使用缓存的场景: 经常查询且不经常改变的数据

一级缓存

一级缓存也叫本地缓存

  • MyBatis中默认开启一级缓存

  • 一级缓存只在一次SqlSession中有效,即在创建SqlSession到关闭SqlSession之间有效

一级缓存失效的情况

  • 增删改操作可能会改变原来的数据,因此必定刷新缓存
  • 查询不同的数据,上一个缓存没有用
  • 查询不同的Mapper.xml
  • 手动清理缓存, 缓存就不存在了

二级缓存

二级缓存也叫全局缓存

  • 二级缓存是基于namespace级别的缓存, 作用域是一个命名空间
  • 工作机制
    • 一个会话查询一条数据,这个数据会放在当前会话的一级缓存
    • 当当前会话关闭时,一级缓存的数据会保存到二级缓存中,一级缓存的数据会被清空
    • 新会话查询数据时会从二级缓存获取
    • 不同Mapper查询的数据会放在自己对应的缓存中

步骤

  • 开启全局缓存, 在mybatis-config.xml配置文件中

    
    <setting name="cacheEnabled" value="true"/>
    
  • 在要使用二级缓存的Mapper中开启缓存

    <cache/>
    

    也可以自定义参数

    <cache
           eviction="FIFO"
           flushInterval="60000"
           size="512"
           readOnly="true"/>
    

二级缓存的异常处理:

  • 在直接使用时,会出现异常Cause: java.io.NotSerializableException: pero.fisher.pojo.Blog

  • 这个错误是由二级缓存使用时,是从一级缓存中获取,实体类需要进行序列化

序列化的方法:

  • 实体类实现序列化接口Serializable即可
  • 实体类尽量都进行序列化,以免出现序列化引发的异常

你可能感兴趣的:(MyBatis学习笔记)