学习,永无止境!|

韩熙隐ario

园龄:2年11个月粉丝:0关注:0

2025-02-10 23:13阅读: 4评论: 0推荐: 0

Mybatis

环境

回顾:

  • JDBC
  • Mysql
  • Java基础
  • Maven
  • junit

SSM框架:配置文件的。最好的方式:看官网文档;

简介

什么是Mybatis

image

  • MyBatis 是一款优秀的持久层框架
  • 它支持定制化 SQL、存储过程以及高级映射。
  • MyBatis 避免了几乎所有的JDBC 代码和手动设置参数以及获取结果集。
  • MyBatis 可以使用简单的 XML 或注解来配置和映射 原生类型、接口和 Java的 POJO(Plain Old JavaObjects,普通老式Java 对象)为数据库中的记录。
  • MyBatis 本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis。
  • 2013年11月迁移到Github。

如何获得Mybatis?

持续化

数据持久化

  • 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
  • 内存:断电即失
  • 数据库(Jdbc),io文件持久化。
  • 生活:冷藏.罐头。

为什么需要需要持久化?

  • 有一些对象,不能让他丢掉。
  • 内存太贵了

持久层

Dao层,Service层,Controller层..

  • 完成持久化工作的代码块
  • 层界限十分明显。

为什么需要Mybatis

  • 帮助程序猿将数据存入到数据库中。

  • 方便

  • 传统的JDBC代码太复杂了。简化。框架。自动化。

  • 不用Mybatis也可以。更容易上手。技术没有高低之分

  • 有点

    • 简单易学
    • 灵活
    • sql和代码的分离,提高了可维护性。
    • 提供映射标签,支持对象与数据库的orm字段关系映射
    • 提供对象关系映射标签,支持对象关系组建维护
    • 提供xml标签,支持编写动态sql

    重要的一点就是使用的人多

    Spring SpringMVC SpringBoot

第一个Mybatis程序

思路:搭建环境-->导入Mybatis-->编写代码-->测试!

搭建环境

搭建数据库

CREATE	DATABASE mybatis;

USE mybatis;

CREATE	TABLE `user` (
	`id` INT(20) NOT NULL PRIMARY KEY,
	`name` VARCHAR(30) DEFAULT NULL,
	`pwd` VARCHAR(30) DEFAULT NULL
)ENGINE	= INNODB DEFAULT CHARSET = utf8;

INSERT INTO `user`(`id`, `name`, `pwd`) VALUES
(1, 'ario', '1223'),
(2, 'ario', '1223'),
(3, 'ario', '1223')

新建项目

  1. 新建一个普通的maven项目

  2. 删除src目录

  3. 导入maven依赖

    <!--  导入依赖-->
      <dependencies>
    <!--    mysql驱动-->
        <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>8.0.20</version>
        </dependency>
        <dependency>
          <groupId>org.mybatis</groupId>
          <artifactId>mybatis</artifactId>
          <version>3.5.2</version>
        </dependency>
    <!--      junit-->
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>3.8.1</version>
          <scope>test</scope>
        </dependency>
      </dependencies>
    

编写代码

可以创建一个子模块,这样子模块可以共用父工程,加号jar包的导入

要看懂中文开发文档

  • 编写mybatis的核心配置文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <!--configuration核心配置文件-->
    <configuration>
    <!--    这里的可以看出,可以有多个部署环境-->
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
    <!--                这里的&amp;与字符串中的&一样-->
                    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"/>
                    <property name="username" value="root"/>
                    <property name="password" value="ario"/>
                </dataSource>
            </environment>
        </environments>
        <!--    每一个Mapper.XML都需要在Mybatis核心配置文件中注册!-->
        <mappers>
            <!--    这里是资源路径,要用斜杠-->
            <mapper resource="com/ario/dao/UserMapper.xml"/>
        </mappers>
    </configuration>
    
  • 编写MybatisUtils工具类

    //sqlSessionFactory -->sqlSession
    public class MybatisUtils {
    
        private static SqlSessionFactory sqlSessionFactory;
    
        static {
            try {
                //使用Mybatis第一步:获版sqlsessionFactory对象,这三行是固定代码
                //用SqlSessionFactoryBuilder实例化sqlSessionFactory
                String resource = "mybatis-config.xml";
                InputStream inputStream = Resources.getResourceAsStream(resource);
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //然有了 sqlsessionFactory,顾名思义,我们就可以从中获得 sqlsession 的实例了。
        //Sqlsession 完全包含了断向数据除执行SOL 命今所游的所有方法。
        public static SqlSession getSqlSession() {
            //用sqlSessionFactory实例化SqlSession,其中SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
            return sqlSessionFactory.openSession();
        }
    }
    
  • 实体类

    public class User {
        private int id;
        private String name;
        private String pwd;
    
        public User() {
        }
    
        public User(int id, String name, String pwd) {
            this.id = id;
            this.name = name;
            this.pwd = pwd;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getPwd() {
            return pwd;
        }
    
        public void setPwd(String pwd) {
            this.pwd = pwd;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", pwd='" + pwd + '\'' +
                    '}';
        }
    }
    
  • Dao接口

    public interface UserDao {
        List<User> getUserList();
    }
    
  • 接口实现类由原来的UserDadimpl转变为一个 Mapper文件,其中用mybatis.user的前提是已经在IDEA中连接数据库,如果没有就直接用表明user

    <?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接口,作用就是之前Dao/Mapper的实现类-->
    <mapper namespace="com.ario.dao.UserDao">
    <!--    select查询语句-->
    <!--    id指定实现类中的方法,resultType指出返回值类型,这样实现类之中的固定代码就简化了-->
        <select id="getUserList" resultType="com.ario.pojo.User">
            select * from mybatis.user
        </select>
    </mapper>
    

测试

junit测试

//测试类所在包名要与java代码一致,这是非强制的规范
public class UserDaoTest {
    @Test
    public void test() {
        //第一步:获取SqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        try {
            //方式一:getMapper(推荐)
            //通过反射获得UserDao对象
            UserDao userDao = sqlSession.getMapper(UserDao.class);
            List<User> userList = userDao.getUserList();

            //方式二:强制类型转换
//        List<User> userList1 = sqlSession.selectList("com.ario.dao.UserDao.getUserList");

            for (User user : userList) {
                System.out.println(user);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            //关闭SqlSession
            sqlSession.close();
        }
    }
}

注意点:

  • 每一个Mapper.XML都需要在Mybatis核心配置文件中注册!

  • 在pom.xml中添加如下配置。通常xml文件放在resource文件中,这里是解决xml在外面无法被识别的问题

    这个点改了好久,之前是在网上找到,今天才发现有错误

    <build>
            <finalName>MybatisFirst</finalName>
            <resources>
                <resource>
                    <directory>src/main/resources</directory>
                    <includes>
                        <include>**/*.properties</include>
                        <include>**/*.xml</include>
                    </includes>
                    <filtering>true</filtering>
                </resource>
                <resource>
                    <directory>src/main/java</directory>
                    <includes>
                        <include>**/*.properties</include>
                        <include>**/*.xml</include>
                    </includes>
                    <filtering>true</filtering>
                </resource>
            </resources>
        </build>
    

CRUD

namespace中的包名要和Dao/mapper接口的包名一致

数据库语句参数

  • id:就是对应的namespace中的方法名;
  • resultType:sql语句执行的返回值!
  • parameterType:参数类型!

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<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/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value=""/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/ario/dao/UserMapper.xml"/>
    </mappers>
</configuration>

MybatisUtils

public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession();
    }
}

pojo

跟上面一样

UserMapper

public interface UserMapper {
    //查询所有用户
    List<User> getUserList();

    //根据id查询用户
    User getUserById(int id);

    //插入数据
    int addUser(User user);

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

    //删除数据
    int deleteUser(int id);

}

UserMapper.xml

<?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">
<mapper namespace="com.ario.dao.UserMapper">
    <select id="getUserList" resultType="com.ario.pojo.User">
        select * from mybatis.user
    </select>

<!--    用#{}传参数-->
    <select id="getUserById" parameterType="int" resultType="com.ario.pojo.User">
        select * from mybatis.user where id = #{id}
    </select>

<!--    #{}中的参数可以直接从User参数中取-->
    <insert id="addUser" parameterType="com.ario.pojo.User">
        insert into mybatis.user (id, name, pwd) values (#{id}, #{name}, #{pwd})
    </insert>

    <update id="updateUser" parameterType="com.ario.pojo.User">
        update mybatis.user set name = #{name}, pwd = #{pwd} where id = #{id}
    </update>

<!--    int类型参数就不用指出了-->
    <delete id="deleteUser">
        delete from mybatis.user where id = #{id}
    </delete>
</mapper>

测试类:UserMapperTest

public class UserMapperTest {

    @Test
    public void test() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            List<User> userList = userMapper.getUserList();

            for (User user : userList) {
                System.out.println(user);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            sqlSession.close();
        }
    }

    @Test
    public void getUserById() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            User user = userMapper.getUserById(1);
            System.out.println(user);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            sqlSession.close();
        }
    }

    @Test
    public void addUser() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            int effect = userMapper.addUser(new User(5, "jack", "232"));
            if (effect > 0) {
                System.out.println("添加成功");
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            //提交事务,否则没用
            sqlSession.commit();
            sqlSession.close();
        }
    }

    @Test
    public void updateUser() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            int effect = userMapper.updateUser(new User(1, "Tom", "111"));
            if (effect > 0) {
                System.out.println("修改成功");
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            sqlSession.commit();
            sqlSession.close();
        }
    }

    @Test
    public void deleteUser() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            int effect = userMapper.deleteUser(2);
            if (effect > 0) {
                System.out.println("删除成功");
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            sqlSession.commit();
            sqlSession.close();
        }
    }
}

万能Map

用map传参

假设,我们的实体类,或者数据库中的表,字段或者参数过多,我们应当考虑使用Map!

//用map修改数据
int updateUserByMap(Map<String, Object> map);
<update id="updateUserByMap" parameterType="map">
    update mybatis.user set name = #{userMame}, pwd = #{pppp}
</update>
map.put("userMame", "python");
map.put("pppp", "888");
int effect = userMapper.updateUserByMap(map);

Map传递参数,直接在sql中取出key即可! 【parameterType="map"】

对象传递参数,直接在sql中取对象的属性即可!【parameterType="Object"】

只有一个基本类型参数的情况下,可以直接在sqI中取到!多个参数用Map,或者注解!

模糊查询

模糊查询怎么写?

本质上还是用到sql中的like,注意sql注入问题

  1. Java代码执行的时候,传递通配符% %

    List<User> userList = mapper.getUserLike("%李%");
    
  2. 在sql拼接中使用通配符!

    select * from mybatis.user where name like "%"#{value}"%"
    

配置解析

image

核心配置文件

环境配置(environments)

MyBatis 可以配置成适应多种环境
不过要记住:尽管可以配置多个环境,但每个SqlSessionFactory实例只能选择一种环境。
学会使用配置多套运行环境!,就是把default属性改成对应的id
Mybatis默认的事务管理器就是JDBC,连接池:POOLED

属性(properties)

我们可以通过properties属性来实现引用配置文件

这些属性都是可外部配置且可动态替换的,既可以在典型的Java 属性文件中配置,亦可通过 properties元素的子元素来传递。【db.properties】

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=root

这里的名字要注意,符合规范,否则会和其他关键字混淆出错,改了好久的bug

在核心配置文件中映入

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--    标签顺序要符合规范-->
    <properties resource="db.properties">
<!--        可以追加属性,但优先级不如外部文件-->
        <property name="username" value="root"/>
        <property name="jdbc.password" value="ario"/>
    </properties>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
        <environment id="test">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/ario/dao/UserMapper.xml"/>
    </mappers>
</configuration>
  • 可以直接引入外部文件
  • 可以在其中增加一些属性配置
  • 如果两个文件有同一个字段,优先使用外部配置文件的!

类型别名(typeAliases)

  • 类型别名是为Java类型设置一个短的名字。
  • 存在的意义仅在于用来减少类完全限定名的冗余

mybatis-config.xml

<typeAliases>
    <typeAlias type="com.ario.pojo.User" alias="User"/>
</typeAliases>

MybatisMapper.xml

<select id="getUserList" resultType="User">
    select * from mybatis.user
</select>

也可以指定一个包名,MyBatis 会在包名下面搜索需要的Java Bean,比如扫描实体类的包,它的默认别名就为这个类的 类名,首字母小写!(这是规范,其实大写也行)

<typeAliases>
    <typeAlias type="com.ario.pojo"/>
</typeAliases>

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

如果实体类十分多,建议使用第二种。

第一种可以DIY别名,第二种则·不行·,如果非要改,需要在实体上增加注解

@Alias("user")
public class User {}

下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。

别名 映射的类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

设置

这是Mybatis中极为重要的调整设置,它们会改变Mybatis的运行时行为

image

image

其他配置

映射器(mappers)

MapperRegistry:注册绑定我们的Mapper文件

方式一:【推荐使用】

<mappers>
<!--        每一个Mapper.xML都需要在Mybatis核心配置文件中注册!-->
	<mapper resource="com/ario/dao/UserMapper.xml"/>
</mappers>

方式二:使用class文件绑定注册

<mappers>
        <mapper class="com.ario.dao.UserMapper"/>
</mappers>

注意点:

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

方式三:使用扫描包进入注入绑定,一下可以全部注入

<mappers>
        <package name="com.ario.dao"/>
</mappers>

注意点:

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

生命周期和作用域

image

生命周期,和作用域,是至关重要的,因为错误的使用会导致非常严重的并发问题

SqlSessionFactoryBuilder:

  • 一旦创建了 SqlSessionFactory,就不再需要它了
  • 局部变量

SqlSessionFactory:

  • 说白了就是可以想象为:数据库连接池
  • SqlSessionFactory一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例
  • 因此 SqlSessionFactory的最佳作用域是应用作用域。
  • 最简单的就是使用单例模式或者静态单例模式。

SqlSession:

  • 连接到连接池的一个请求!
  • SalSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
  • 用完之后需要赶紧关闭,否则资源被占用!

image

这里面的每一个Mapper,就代表一个具体的业务!

ResultMap结果集映射

解决属性名和字段名不一致的问题

数据库中的字段

image

新建一个项目,拷贝之前的,测试实体类字段不一致的情况

public class User {
    private int id;
    private String name;
    private String password;
}

出现的问题:

image

<!--    select * from mybatis.user where id = #{id}-->
<!--    类处理器,去找与数据库列名一样的属性名,自动转义,但是不一致时无效-->
<!--    select id,name,pwd from mybatis.user where id = #{id}-->

解决方法:

  • 起别名

    <select id="getUserById" parameterType="int" resultType="com.ario.pojo.User">
        select id,name,pwd as password from mybatis.user where id = #{id}
    </select>
    
  • 用结果集映射

    <!--    结果集映射-->
    <resultMap id="UserMap" type="User">
        <!--        column数据库中的字段,property实体类中的属性,两者相同的可以不写-->
        <!--        <result column="id" property="id"/>-->
        <!--        <result column="name" property="name"/>-->
        <result column="pwd" property="password"/>
    </resultMap>
    
    <select id="getUserById" resultMap="UserMap">
        select * from mybatis.user where id = #{id}
    </select>
    
    • 上述语句只是简单地将所有的列映射到 HashMap 的键上,这由 resultType 属性指定。虽然在大部分情况下都够用,但是 HashMap 并不是一个很好的领域模型。你的程序更可能会使用 JavaBean 或 POJO(Plain Old Java Objects,普通老式 Java 对象)作为领域模型。MyBatis 对两者都提供了支持
    • resultMap 元素是 MyBatis 中最重要最强大的元素
    • ResultMap 的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系就行了。
    • ResultMap 最优秀的地方在于,虽然你已经对它相当了解了,但是根本就不需要显式地用到他们,什么不一样就显示映射谁

日志

日志工厂

如果一个数据库操作,出现了异常,我们需要排错。日志就是最好的助手!

曾经:sout、debug

现在:日志工厂!

image

  • SLF4J
  • LOG4J【掌握】
  • LOG4J2
  • JDK_LOGGING
  • COMMONS_LOGGING
  • STDOUT_LOGGING【掌握】
  • NO_LOGGING

在Mybatis中具体使用那个一日志实现,在核心配置文件设定

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

STDOUT_LOGGING标准日志输出

image

Log4j

什么是Log4j?

  • Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
  • 我们也可以控制每一条日志的输出格式;
  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
  • 通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
  1. 先导入log4j的包

    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>
    
  2. log4j.properties资源配置文件

    #将等级为DEBUG的日志信息输出到console和file这两个目的地,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=[%c]-%m%n
    
    #文件输出的相关设置
    log4j.appender.file = org.apache.log4j.RollingFileAppender
    log4j.appender.file.File=./log/kuang.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
    
  3. 配置log4j为日志的实现

    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
    
  4. Log4j的使用!,直接测试运行

    image

简单使用:

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

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

    static Logger logger = Logger.getLogger(UserMapperTest.class);
    
  3. 常用日志级别

    logger.info("info:成功了!");
    logger.debug("debug:成功了!");
    logger.error("error:成功了!");
    
  4. image

分页

思考:为什么要分页?

  • 减少数据的处理量

使用Limit分页

语法:SELECT * FROM user LIMIT startIndex, pageSize;
SELECT * FROM user LIMIT 3; #[0,n]

使用Mybatis实现分页,核心SQL

  1. 接口

    //分页
    List<User> getUserByLimit(Map<String,Integer> map);
    
  2. Mapper.xml

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

    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    HashMap<String, Integer> map = new HashMap<String, Integer>();
    map.put("startIndex", 1);
    map.put("pageSize", 2);
    List<User> users = userMapper.getUserByLimit(map);
    for (User user : users) {
        System.out.println(user);
    }
    

RowBounds

了解

不再使用SQL实现分页

  1. 接口

    //利用RowBounds进行分页
    List<User> getUserbyRowBounds();
    
  2. mapper.xml

    <select id="getUserbyRowBounds" resultMap="UserMap">
    	select * from mybaties.user
    </select>
    
  3. junit测试

    @Test
        public void RowBoundstest() {
            //利用工具类获取SqlSession
             SqlSession sqlSession = MyBatisUtil.getSqlSession();
             RowBounds rowBounds = new RowBounds(5, 10);
             List<User> userList = sqlSession.selectList("com.jms.dao.UserMapper.getUserbyRowBounds",
                     null, rowBounds);
            for (User user : userList) {
                System.out.println(user);
            }     sqlSession.close();
        }
    

分页插件

image

使用注解开发

面向接口编程

  • 大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程
  • 根本原因:解耦,可拓展,提高复用,分层开发中,上层不用管具体的实现,大家都遵守共同的标准,使得开发变得容易,规范性更好
  • 在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;
  • 而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

关于接口的理解

  • 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离
  • 接口的本身反映了系统设计人员对系统的抽象理解。接口应有两类:
    • 第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);
    • 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface);
  • 一个体有可能有多个抽象面。抽象体与抽象面是有区别的。

三个面向区别

  • 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法
  • 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现.
  • 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题,更多的体现就是对系统整体的架构

使用注解开发

  1. 接口

    @Select("select * from user")
    List<User> getUserList();
    
  2. mybatis-config.xml。mapper.xml文件不再需要,当然也可以存在,用来满足复杂需求

    <mappers>
            <mapper class="com.ario.dao.UserMapper"/>
    </mappers>
    
  3. junit测试

    @Test
        public void test() {
            SqlSession sqlSession = MybatisUtils.getSqlSession();
    
            try {
                //底层主要用反射
                UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
                List<User> userList = userMapper.getUserList();
    
                for (User user : userList) {
                    System.out.println(user);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                sqlSession.close();
            }
        }
    

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

本质:反射机制实现
底层:动态代理!

image

Mybatis执行流程剖析

可以通过debug看出
image

CURD

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

public static SqlSession getSqlSession() {
    return sqlSessionFactory.openSession(true);
}

编写接口,增加注解

public interface UserMapper {
    @Select("select * from user")
    List<User> getUserList();

//    方法存在多个参数,所行的参数前面必须加上@Param("id”)注解
//    sql语句#{}中的数据就是在@Param("id”)注解取的,要保持一致
    @Select("select * from user where id = #{id}")
    User getUserById(@Param("id") int id);

    @Insert("insert into user(id, name, pwd) values (#{id}, #{name}, #{pwd})")
    int addUser(User user);

    @Update("update user set name=#{name},pwd=#{pwd} where id = #{id}")
    int updateUser(User user);

    @Delete("delete from user where id = #{id}")
    int deleteUser(@Param("id") int id);
}

测试类

public class UserMapperTest {

    @Test
    public void test() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        try {
            //底层主要用反射
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            List<User> userList = userMapper.getUserList();

            for (User user : userList) {
                System.out.println(user);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            sqlSession.close();
        }
    }

    @Test
    public void getUserById() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        try {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            System.out.println(mapper.getUserById(1));
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            sqlSession.close();
        }
    }

    @Test
    public void addUser() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        try {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            mapper.addUser(new User(2, "jack", "666"));
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            sqlSession.close();
        }
    }

    @Test
    public void updateUser() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        try {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            mapper.updateUser(new User(5, "Tom", "520"));
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            sqlSession.close();
        }
    }

    @Test
    public void deleteUser() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        try {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            mapper.deleteUser(3);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            sqlSession.close();
        }
    }
}

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

关于@Param()注解

  • 基本类型的参数或者String类型,需要加上
  • 引用类型不需要加
  • 如果只有一个基本类型的话,可以忽略,但是建议大家都加上!
  • 我们在SQL中引用的就是我们这里的 @Param() 中设定的属性名!

#{} ${}的区别

  • #{}:主要用于预编译 SQL 语句中的参数占位符。MyBatis 会将 #{} 标识的内容处理成 PreparedStatement 对象的参数,这样可以有效防止 SQL 注入攻击。
  • ${}:用于直接字符串替换,MyBatis 会将 ${} 中的内容直接替换到 SQL 语句中,不会进行预编译处理。

Lombok

java library
plugs
build toolswith one annotation your class

使用步骤:

  1. 在IDEA中安装Lombok插件!

    image

  2. 在项目中导入lombok的jar包,

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.10</version>
    </dependency>
    
  3. 在类上加注解即可

    @Alias("user")
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Getter
    @Setter
    public class User {
        private int id;
        private String name;
        private String pwd;
    }
    

    @Getter and @Setter
    @FieldNameConstants
    @ToString
    @EqualsAndHashCode
    @AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
    @Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
    @Data
    @Builder
    @SuperBuilder
    @Singular
    @Delegate
    @Value
    @Accessors

  4. 优缺点

    image

多对一处理

测试环境搭建

  1. 分析问题

    image

    • 多个学生,对应一个老师对于
    • 学生这边而言, 关联. 多个学生,关联一个老师 【多对一】
    • 对于老师而言,集合,一个老师有很多学生【一对多】
  2. 数据库搭建

    CREATE TABLE `teacher` (
      `id` INT(10) NOT NULL,
      `name` VARCHAR(30) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=INNODB DEFAULT CHARSET=utf8
    INSERT INTO teacher(`id`, `name`) VALUES (1, 'ario'); 
    
    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
    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');
    
  3. 导入lombok

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.10</version>
    </dependency>
    
  4. 新建实体类 Teacher,Student

    @Data
    public class Student {
        private int id;
        private String name;
    //    学生需要管理一个老师
        private Teacher teacher;
    }
    
    @Data//GET,SET,ToString,有参,无参构造
    public class Teacher {
        private int id;
        private String name;
    }
    
  5. 建立Mapper接口

    public interface TeacherMapper {
        @Select("select * from teacher where id = #{tid}")
        Teacher getTeacherById(@Param("tid") int id);
    }
    
  6. 建立Mapper.XML文件

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

    <mappers>
        <package name="com.ario.dao"/>
    </mappers>
    
  8. 测试查询是否能够成功!

    @Test
    public void getTeacherById() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
    
        try {
            TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
            Teacher teacher = mapper.getTeacherById(1);
            System.out.println(teacher);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            sqlSession.close();
        }
    }
    
  9. 最终项目结构图,这里Mapper.xml不止一个,可以在resources文件夹下集中安置,注意,com.ario.dao要一级一级建否则可能会出错

    image

按查询嵌套处理

  • 给StudentMapper接口增加方法

    List<Student> getStudents();
    
  • 编写对应的Mapper文件

    <?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">
    <mapper namespace="com.ario.dao.StudentMapper">
        <!--
        需求:获取所有学生及对应老师的信息
        思路:
            1. 获取所有学生的信息
            2. 根据获取的学生信息的老师ID->获取该老师的信息
            3. 思考问题,这样学生的结果集中应该包含老师,该如何处理呢,数据库中我们一般使用关联查询?
                1. 做一个结果集映射:StudentTeacher
                2. StudentTeacher结果集的类型为 Student
                3. 学生中老师的属性为teacher,对应数据库中为tid。
                   多个 [1,...)学生关联一个老师=> 一对一,一对多
                4. 查看官网找到:association – 一个复杂类型的关联;使用它来处理关联查询
        -->
        <select id="getStudents" resultMap="StudentTeacher">
            select * from student
        </select>
        <resultMap id="StudentTeacher" type="Student">
            <!--association关联属性  property属性名 javaType属性类型 column在多的一方的表中的列名-->
            <association property="teacher"  column="tid" javaType="Teacher" select="getTeacher"/>
    <!--        复杂的属性,我们需要单独处理
                对象:association
                集合:collection-->
        </resultMap>
        <!--
        这里传递过来的id,只有一个属性的时候,下面可以写任何值
        association中column多参数配置:
            column="{key=value,key=value}"
            其实就是键值对的形式,key是传给下个sql的取值名称,value是片段一中sql查询的字段名。
        -->
    <!--    这里的#{id}中的名字随便写一下,这里比较模糊,mybatis会推断类型-->
        <select id="getTeacher" resultType="teacher">
            select * from teacher where id = #{id}
        </select>
    </mapper>
    

按结果嵌套处理

  • 给StudentMapper接口增加方法

    List<Student> getStudents2();
    
  • 编写对应的Mapper文件

    <!--    按照结果嵌套处理-->
        <select id="getStudents2" resultMap="StudentTeacher2">
            select s.id sid, s.name sname, t.name tname
            from mybatis.student s, mybatis.teacher t
            where s.tid = t.id
        </select>
    
    <!--    这里的column实际上是与上面sql语句对应的-->
        <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>
    

小结

  • 按照查询进行嵌套处理就像SQL中的子查询
  • 按照结果进行嵌套处理就像SQL中的联表查询【推荐使用,结构清晰】

一对多的处理

比如:一个老师拥有多个学生!
对于老师而言,就是一对多的关系!

实体类编写

@Data
public class Student {
    private int id;
    private String name;
    private int tid;
}
@Data//GET,SET,ToString,有参,无参构造
public class Teacher {
    private int id;
    private String name;

    private List<Student> students;
}

按结果嵌套处理

  1. TeacherMapper接口编写方法

    List<Teacher> getTeacher(int id);
    
  2. 编写接口对应的Mapper配置文件

    <?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">
    <mapper namespace="com.ario.dao.TeacherMapper">
    
        <!--
        思路:
            1. 从学生表和老师表中查出学生id,学生姓名,老师姓名
            2. 对查询出来的操作做结果集映射
                1. 集合的话,使用collection!
                    JavaType和ofType都是用来指定对象类型的
                    JavaType是用来指定pojo中属性的类型
                    ofType指定的是映射到list集合属性中pojo的类型。
                    javaType="”指定属性的类型!
                    集合中的泛型信息,我们使用ofType获取
        -->
        <select id="getTeacher" 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=#{id}
        </select>
        <resultMap id="TeacherStudent" type="Teacher">
            <result  property="name" column="tname"/>
            <collection property="students" ofType="Student">
                <result property="id" column="sid" />
                <result property="name" column="sname" />
                <result property="tid" column="tid" />
            </collection>
        </resultMap>
    </mapper>
    
  3. 将Mapper文件注册到MyBatis-config文件中

    <mappers>
            <package name="com.ario.dao"/>
    </mappers>
    
  4. 测试

    @Test
        public void getTeacher() {
            SqlSession sqlSession = MybatisUtils.getSqlSession();
    
            try {
                TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
                List<Teacher> teachers = mapper.getTeacher(1);
    
                for (Teacher teacher : teachers) {
                    System.out.println(teacher);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                sqlSession.close();
            }
        }
    

按查询嵌套处理

  1. TeacherMapper接口编写方法

    List<Teacher> getTeacher2(int id);
    
  2. 编写接口对应的Mapper配置文件

    <select id="getTeacher2" resultMap="TeacherStudent2">
            select * from teacher where id = #{id}
        </select>
        <resultMap id="TeacherStudent2" type="Teacher">
            <!--column是一对多的外键 , 写的是一的主键的列名-->
            <collection property="students" javaType="ArrayList" ofType="Student" column="id" select="getStudentByTeacherId"/>
        </resultMap>
        <select id="getStudentByTeacherId" resultType="Student">
            select * from student where tid = #{id}
        </select>
    
  3. 测试

    @Test
        public void getTeacher2() {
            SqlSession sqlSession = MybatisUtils.getSqlSession();
    
            try {
                TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
                List<Teacher> teachers = mapper.getTeacher2(1);
    
                for (Teacher teacher : teachers) {
                    System.out.println(teacher);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                sqlSession.close();
            }
        }
    

小结

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

注意点:

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

慢SQL 1s vs 1000s

提升查询速度要不断优化,理解底层

面试高频

  • Mysql引擎
  • InnoDB底层原理
  • 索引
  • 索引优化!

动态SQL

介绍

什么是动态SQL:动态SQL指的是根据不同的查询条件 , 生成不同的Sql语句.

官网描述:
    MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。
    虽然在以前使用动态 SQL 并非一件易事,但正是 MyBatis 提供了可以被用在任意 SQL 映射语句中的强大的动态 SQL 语言得以改进这种情形。
    动态 SQL 元素和 JSTL 或基于类似 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多元素需要花时间了解。MyBatis 3 大大精简了元素种类,现在只需学习原来一半的元素便可。MyBatis 采用功能强大的基于 OGNL 的表达式来淘汰其它大部分元素。
    -------------------------------
    - if
    - choose (when, otherwise)
    - trim (where, set)
    - foreach
    -------------------------------

我们之前写的 SQL 语句都比较简单,如果有比较复杂的业务,我们需要写复杂的 SQL 语句,往往需要拼接,而拼接 SQL ,稍微不注意,由于引号,空格等缺失可能都会导致错误。

  那么怎么去解决这个问题呢?这就要使用 mybatis 动态SQL,通过 if, choose, when, otherwise, trim, where, set, foreach等标签,可组合成非常灵活的SQL语句,从而在提高 SQL 语句的准确性的同时,也大大提高了开发人员的效率。

搭建环境

  1. 新建数据库

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

    image

  3. IDUtils

    //生成随机的id
    public class IDUtils {
        public static String getId() {
            return UUID.randomUUID().toString().replaceAll("-", "");
        }
    
        @Test
        public void test() {
            System.out.println(IDUtils.getId());
        }
    }
    
  4. 实体类

    @Data
    public class Blog {
        private String id;
        private String title;
        private String author;
        private Date createTime;
        private int views;
    }
    
  5. dao包下

    public interface BlogMapper {
        int addBlog(Blog blog);
    }
    
    <?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">
    <mapper namespace="com.ario.dao.BlogMapper">
    
        <insert id="addBlog" parameterType="Blog">
            insert into mybatis.blog (id, title, author, create_time, views)
            values (#{id}, #{title}, #{author}, #{createTime}, #{views})
        </insert>
    </mapper>
    
  6. 核心配置文件

    <settings>
    <!--        标准的日志工厂实现-->
            <setting name="logImpl" value="STDOUT_LOGGING"/>
    <!--    是否开启自动驼峰命名规则(camelcase)映射,
            即从经典数据库列名A_COLUMN (因为使用驼峰自动大写后就看不出了)
            到经典 Java 属性名 aColumn 的类似映射。-->
            <setting name="mapUnderscoreToCamelCase" value="true"/>
        </settings>
    
  7. 测试类

    public class MyTest {
    
        @Test
        public void addBlog() {
            SqlSession sqlSession = MybatisUtils.getSqlSession();
    
            try {
                BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
                Blog blog = new Blog();
                blog.setId(IDUtils.getId());
                blog.setTitle("Mybatis如此简单");
                blog.setAuthor("狂神说");
                blog.setCreateTime(new Date());
                blog.setViews(9999);
                mapper.addBlog(blog);
                blog.setId(IDUtils.getId());
                blog.setTitle("Java如此简单");
                mapper.addBlog(blog);
                blog.setId(IDUtils.getId());
                blog.setTitle("Spring如此简单");
                mapper.addBlog(blog);
                blog.setId(IDUtils.getId());
                blog.setTitle("微服务如此简单");
                mapper.addBlog(blog);
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                sqlSession.close();
            }
        }
    
    }
    

if语句

需求:根据作者名字和博客名字来查询博客!如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询

<select id="queryBlogIf" parameterType="map" resultType="Blog">
    select * from mybatis.blog where 1 = 1
    <if test="title != null">
        and title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</select>

上面的where 1 =1不符合规范,可以用where语句,这样可以合理的添加and/or语句,是sq正确

where语句

这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。

<select id="queryBlogIf" parameterType="map" resultType="blog">
    select * from blog 
    <where>
        <if test="title != null">
            title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </where>
</select>

choose、when、otherwise

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

set语句

同理,上面的对于查询 SQL 语句包含 where 关键字,如果在进行更新操作的时候,含有 set 关键词,我们怎么处理呢?

<!--注意set是用的逗号隔开-->
<update id="updateBlog" parameterType="map">
    update blog
      <set>
          <if test="title != null">
              title = #{title},
          </if>
          <if test="author != null">
              author = #{author}
          </if>
      </set>
    where id = #{id};
</update>

trim语句

可以通过自定义 trim 元素来定制 where 元素的功能

<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。查前缀

set 元素等价的自定义 trim 元素吧:

查后缀

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

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

if,where,set,choose ,when

SQL片段

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

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

    <sql id="if-title-author">
        <if test="title != null">
            title == #{title}
        </if>
        <if test="author != null">
            author == #{author}
        </if>
    </sql>
    
  2. 在需要使用的地方使用include标签引用即可

    <select id="queryBlogIf" parameterType="map" resultType="Blog">
        select * from mybatis.blog
        <where>
            <include refid="if-title-author"></include>
        </where>
    </select>
    

注意事项:

  • 最好基于单表来定义SQL片段!【否则会很混乱】
  • 不要存在where标签【方便复用】

foreach

image

将数据库中前三个数据的id修改为1,2,3;

需求:我们需要查询 blog 表中 id 分别为1,2,3的博客信息

  1. 编写接口

    List<Blog> queryBlogForeach(Map map);
    
  2. 编写SQL语句

    <select id="queryBlogForeach" parameterType="map" resultType="blog">
        select * from blog
        <where>
            <!--
            collection:指定输入对象中的集合属性
            item:每次遍历生成的对象
            open:开始遍历时的拼接字符串
            close:结束时拼接的字符串
            separator:遍历对象之间需要拼接的字符串
            select * from blog where 1=1 and (id=1 or id=2 or id=3)
          -->
            <foreach collection="ids"  item="id" open="and (" close=")" separator="or">
                id=#{id}
            </foreach>
        </where>
    </select>
    
  3. 测试

    @Test
    public void testQueryBlogForeach(){
        SqlSession session = MybatisUtils.getSession();
        BlogMapper mapper = session.getMapper(BlogMapper.class);
        HashMap map = new HashMap();
        List<Integer> ids = new ArrayList<Integer>();
        ids.add(1);
        ids.add(2);
        ids.add(3);
        map.put("ids",ids);
        List<Blog> blogs = mapper.queryBlogForeach(map);
        System.out.println(blogs);
        session.close();
    }
    

小结:其实动态 sql 语句的编写往往就是一个拼接的问题,为了保证拼接准确,我们最好首先要写原生的 sql 语句出来,然后在通过 mybatis 动态sql 对照着改,防止出错。多在实践中使用才是熟练掌握它的技巧

缓存

简介

查询 : 连接数据库,消耗资源
一次查询的结果,给他暂存在一个可以直接取到的地方!-->内存:缓存
我们再次查询相同数据的时候,直接走缓存,就不用走数据库了
  1. 什么是缓存 [ Cache ]?
    • 存在内存中的临时数据。
    • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
  2. 为什么使用缓存?
    • 减少和数据库的交互次数,减少系统开销,提高系统效率。
  3. 什么样的数据能使用缓存?
    • 经常查询并且不经常改变的数据。

Mybatis缓存

  • MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
  • MyBatis系统中默认定义了两级缓存:一级缓存二级缓存
    • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存,close以后就没了
    • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存【mapper级别】。
    • 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

一级缓存

  • 一级缓存也叫本地缓存:
    • 与数据库同一次会话期间查询到的数据会放在本地缓存中。
    • 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;

测试步骤

  1. 开启日志!

    <settings>
    <!--        标准的日志工厂实现-->
            <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    
  2. 测试在一个Sesion中查询两次相同记录

    @Test
    public void getUserById() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        try {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            System.out.println(mapper.getUserById(1));
            System.out.println("---------分隔符---------");
            System.out.println(mapper.getUserById(1));
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            sqlSession.close();
        }
    }
    
  3. 查看日志输出

    image

基本上就是这样。这个简单语句的效果如下:

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存【缓存失效】。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存【缓存失效】。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
  • 一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它;

一级缓存失效(没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求!)的四种情况

  1. sqlSession不同

    @Test
    public void testQueryUserById(){
        SqlSession session = MybatisUtils.getSession();
        SqlSession session2 = MybatisUtils.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        UserMapper mapper2 = session2.getMapper(UserMapper.class);
        User user = mapper.queryUserById(1);
        System.out.println(user);
        User user2 = mapper2.queryUserById(1);
        System.out.println(user2);
        System.out.println(user==user2);
        session.close();
        session2.close();
    }
    

    观察结果:发现发送了两条SQL语句!

    结论:每个sqlSession中的缓存相互独立

  2. sqlSession相同,查询条件不同。因为当前缓存中,不存在这个数据

  3. sqlSession相同,两次查询之间执行了增删改操作!因为增删改操作可能会对当前数据产生影响

  4. sqlSession相同,手动清除一级缓存,中间加这个语句session.clearCache();//手动清除缓存

一级缓存就是一个map

二级缓存

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

步骤:

  1. 开启全局缓存【mybatis-config.xml】

    <settings>
    <!--        标准的日志工厂实现-->
            <setting name="logImpl" value="STDOUT_LOGGING"/>
            <setting name="cacheEnabled" value="true"/>
    </settings>
    
  2. 在mapper.xml中配置使用二级缓存

    <!--一个标签就开了-->
    <cache/>
    

    也可以自定义一些配置

    <cache
            eviction="FIFO"
            flushInterval="60000"
            size="512"
            readOnly="true"/>
    
  3. 测试代码

    • 如果配置的<cache/>,要把实体类序列化才可以

      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      public class User implements Serializable {
          private int id;
          private String name;
          private String pwd;
      }
      
    • 测试

      @Test
      public void getUserById() {
          SqlSession sqlSession1 = MybatisUtils.getSqlSession();
          SqlSession sqlSession2 = MybatisUtils.getSqlSession();
          UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
          UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
          System.out.println(mapper1.getUserById(1));
          sqlSession1.close();
          System.out.println("---------分隔符---------");
          System.out.println(mapper2.getUserById(1));
          sqlSession2.close();
      }
      

      image

小结:

  • 只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据
  • 查出的数据都会被默认先放在一级缓存中
  • 只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中

Mybatis缓存原理

image

通过理解原理可以优化代码,比如在SQL语句中设置相关代码

<select id="getUserById" parameterType="int" resultType="User" useCache="false">
    select * from mybatis.user where id = #{id}
</select>

<insert id="addUser" parameterType="User" flushCache="false">
    insert into mybatis.user (id, name, pwd) values (#{id}, #{name}, #{pwd})
</insert>

自定义缓存-ehcahce

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

  1. 使用ehcache,先要导包

    <dependency>
        <groupId>org.mybatis.caches</groupId>
        <artifactId>mybatis-ehcache</artifactId>
        <version>1.1.0</version>
    </dependency>
    
  2. 在mapper中指定使用我们的ehcache缓存实现!

    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
    
  3. 编写ehcache.xml文件,如果在加载时未找到/ehcache.xml资源或出现问题,则将使用默认配置。

    <?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:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
           user.home – 用户主目录
           user.dir  – 用户当前工作目录
           java.io.tmpdir – 默认临时文件路径
         -->
        <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"/>
        <!--
           defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
         -->
        <!--
          name:缓存名称。
          maxElementsInMemory:缓存最大数目
          maxElementsOnDisk:硬盘最大缓存个数。
          eternal:对象是否永久有效,一但设置了,timeout将不起作用。
          overflowToDisk:是否保存到磁盘,当系统当机时
          timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
          timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
          diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
          diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
          diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
          memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
          clearOnFlush:内存数量最大时是否清除。
          memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
          FIFO,first in first out,这个是大家最熟的,先进先出。
          LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
          LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
       -->
    </ehcache>
    

    Redis数据库来做缓存! K-V

本文作者:韩熙隐ario

本文链接:https://www.cnblogs.com/arioya/p/18708956

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   韩熙隐ario  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起