Mybatis

1.Mybatis简介

  • MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

  • 官方中文文档 :mybatis – MyBatis 3 | 简介

持久化:

  • 持久化是将程序数据在持久状态和瞬时状态间转换的机制。通俗的讲,就是瞬时数据(比如内存中的数据,是不能永久保存的)持久化为持久数据(比如持久化至数据库中,能够长久保存)

1.1、mybatis特点

  • 简单易学:本身就很小且简单。
  • 灵活: sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
  • 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
  • 提供映射标签,支持对象与数据库的ORM字段关系映射。
  • 提供对象关系映射标签,支持对象关系组建维护。
  • 提供xml标签,支持编写动态sql

1.2、mybatis中#和$的区别

1、#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。
如:where username=#{username},如果传入的值是111,那么解析成sql时的值为where username=“111”, 如果传入的值是id,则解析成的sql为where username=“id”.

2、 将传入的数据直接显示生成在 s q l 中。 ∗ ∗ ∗ ∗ 如: w h e r e u s e r n a m e = 将传入的数据直接显示生成在sql中。** **如:where username= 将传入的数据直接显示生成在sql中。如:whereusername={username},如果传入的值是111,那么解析成sql时的值为where username=111;
如果传入的值是;drop table user;,则解析成的sql为:select id, username, password, role from user where username=;drop table user;

3、#方式能够很大程度防止sql注入,$方式无法防止Sql注入。

4、$方式一般用于传入数据库对象,例如传入表名.

5、一般能用#的就别用 ,若不得不使用“ ,若不得不使用“ ,若不得不使用{xxx}”这样的参数,要手工地做好过滤工作,来防止sql注入攻击。

6、在MyBatis中,“ x x x ”这样格式的参数会直接参与 S Q L 编译,从而不能避免注入攻击。但涉及到动态表名和列名时,只能使用“ {xxx}”这样格式的参数会直接参与SQL编译,从而不能避免注入攻击。但涉及到动态表名和列名时,只能使用“ xxx这样格式的参数会直接参与SQL编译,从而不能避免注入攻击。但涉及到动态表名和列名时,只能使用{xxx}”这样的参数格式。所以,这样的参数需要我们在代码中手工进行处理来防止注入。
【结论】在编写MyBatis的映射语句时,尽量采用“#{xxx}”这样的格式。若不得不使用“${xxx}”这样的参数,要手工地做好过滤工作,来防止SQL注入攻击。

2.Mybatis的使用

2.1、导入依赖包

<!--mybatis-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.10</version>
</dependency>

<!--导入MySQL连接-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

<!--导入单元测试Junit-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>RELEASE</version>
</dependency>

2.2、配置Mybatis

1.在resources目录中创建Mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://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.jdbc.Driver"/>
                <!--数据库地址-->
                <!--&amp;  为转义 &amp;-> &  -->
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=true"/>
                <!--用户名-->
                <property name="username" value="root"/>
                <!--密码-->
                <property name="password" value="123456"/>
            </dataSource>

        </environment>
    </environments>

    <!--注册mapper,没有会报错-->
    <mappers>
        <!--resource: xml所在路径-->
        <mapper resource="org/mybatis/example/BlogMapper.xml"/>
    </mappers>

</configuration>

2.3、 创建MybatisUtils工具类

  • 作用:从 SqlSessionFactory 中获取 SqlSession 的对象
public class MybatisUtils {

    //sqlSession工厂类对象
    private static SqlSessionFactory sqlSessionFactory = null;

    //创建静态代码快,加载类时自动执行
    static {

        try {
            //加载配置文件
            String resource = "mybatis-config.xml";

            InputStream inputStream = null;

            //将资源转换为流
            inputStream = Resources.getResourceAsStream(resource);

            //获取工厂类对象
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        } catch (
                IOException e) {
            throw new RuntimeException(e);
        }
        
    }
    //通过工厂类对象获取SqlSession对象
    public static SqlSession getSqlSession(){
        //返回一个开启事务自动提交的sqlSession对象
        return sqlSessionFactory.openSession(true);
    }
    
    //重载方法,通过传入参数确定是否需要自动提交事务
    public static SqlSession getSqlSession(boolean flag){
        //返回一个开启事务自动提交的sqlSession对象
        return sqlSessionFactory.openSession(flag);
    }
}

2.4、创建Mapper接口

1.连接数据库,并创建对应数据库实体类(pojo)

package com.mixcus.pojo;

/**
 * &#064;@BelongsProject: Mybatis-study
 * &#064;@BelongsPackage: com.mixcus.pojo
 * &#064;@FileName: User
 * &#064;@Author: 86151
 * &#064;@Date: 2023/7/6-20:53
 * &#064;@Version: 1.0
 * &#064;@Description:
 */

public class User {

    private int id ;

    private String name ;

    private String password ;

    public User() {
    }

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

    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 getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

2.创建对应mapper接口(类似于Dao层的接口)

package com.mixcus.mapper;

import com.mixcus.pojo.User;

import java.util.List;
import java.util.Map;


public interface UserMapper {

    //获取用户列表
    public List<User> getUserList();

    //添加用户
    public boolean addUser(User user);

    //更新用户
    public boolean updateUserById(User user);

    //删除用户
    public boolean deleteUserById(int id);

}

3.在项目的pom.xml中配置resource

  • 解决xml或properties文件不会被导出的问题
<!--在build中配置resources,来防止资源导出失败的问题-->
<build>
    <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>

4.创建对应的mapper.xml实现CRUD

  • 注:将 https —> http 可以实现sql语句提示效果
<?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: 绑定该xml对应的mapper接口-->
<mapper namespace="com.mixcus.mapper.UserMapper">


    <!--增-->
    <insert id="addUser" parameterType="com.mixcus.pojo.User">
        insert into mybatis.user (`id`,`name`,`password`) values (#{id},#{name},#{password})
    </insert>

    <!--删-->
    <delete id="deleteUserById" parameterType="int">
        delete from mybatis.user where id = #{id}
    </delete>

    <!--查-->
    <select id="getUserList" resultType="com.mixcus.pojo.User">
        select * from mybatis.user
    </select>

    <!--改-->
    <update id="updateUserById" parameterType="com.mixcus.pojo.User">
        update mybatis.user set name = #{name},password = #{password} where id = #{id}
    </update>

</mapper>

5.使用@param为参数命名传递多个参数

Usermapper:

public interface UserMapper{
   public List<User> getUserList(@Param("name") string name,@Param("id") int id);
}

Usermapper.xml:

<!--查-->
<select id="getUserList" resultType="com.mixcus.pojo.User">
    select * from mybatis.user where name = #{name} and id = #{id}
</select>

2.5、测试CRUD

  • 在test目录中添加对应包

  • 创建测试类

  • 使用junit @Test测试

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

package com.mixcus.mapper;

import com.mixcus.pojo.User;
import com.mixcus.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.List;
import java.util.Objects;

/**
 * &#064;@BelongsProject: Mybatis-study
 * &#064;@BelongsPackage: com.mixcus.mapper
 * &#064;@FileName: UserMapperTest
 * &#064;@Author: 86151
 * &#064;@Date: 2023/7/6-21:04
 * &#064;@Version: 1.0
 * &#064;@Description:
 */

public class UserMapperTest {

    @Test
    public void select(){
        //获取session
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        //获取mapper接口的对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        List<User> userList = mapper.getUserList();

        for (User user : userList) {
            System.out.println(user);
        }
        //关闭sqlSession
        sqlSession.close();
    }
    @Test
    public void addUser(){
        //获取session
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        //获取mapper接口的对象 类似于获取UserDao的对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        boolean result = mapper.addUser(new User(5, "赵六", "111111"));

        //注意 增删改需要提交事务  对数据库数据产生影响]
        sqlSession.commit();

        if(result){
            System.out.println("添加成功");
        }else
            System.out.println("添加失败!");

        //关闭sqlSession
        sqlSession.close();

    }
    @Test
    public void updateUserById(){
        //获取session
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        //获取mapper接口的对象 类似于获取UserDao的对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        boolean result = mapper.updateUserById(new User(5,"哈哈","222222"));

        //注意 增删改需要提交事务  对数据库数据产生影响]
        sqlSession.commit();

        if(result){
            System.out.println("修改成功");
        }else
            System.out.println("修改失败!");

        //关闭sqlSession
        sqlSession.close();

    }
    //删除用户
    @Test
    public void deleteUserById(){
        //获取session
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        //获取mapper接口的对象 类似于获取UserDao的对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        boolean result = mapper.deleteUserById(5);

        //注意 增删改需要提交事务  对数据库数据产生影响]
        sqlSession.commit();

        if(result){
            System.out.println("删除成功");
        }else
            System.out.println("删除失败!");

        //关闭sqlSession
        sqlSession.close();

    }

}

2.6 Map和模糊查询

  • map设置key的值 ,sql语句的值通过value获取
@Test
public void updateUserById(){
    //获取session
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    //获取mapper接口的对象 类似于获取UserDao的对象
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    Map<String, Object> map = new HashMap<>();
    map.put("id","1");
    map.put("name","jack");
    map.put("password","666");

    boolean result = mapper.updateUserById(map);

    //注意 增删改需要提交事务  对数据库数据产生影响]
    sqlSession.commit();

    if(result){
        System.out.println("修改成功");
    }else
        System.out.println("修改失败!");

    //关闭sqlSession
    sqlSession.close();

}
  • xml
<!--改-->
<update id="updateUserById" parameterType="map">
    <!--参数名字就是value的值,可以随便取名,只要与传入的map的value相同就行-->
    update mybatis.user set name = #{name},password = #{password} where id = #{id}
</update>

模糊查询:

  • 使用concat进行字符串的拼接

    <!--查-->
    <select id="getUserListByName" resultType="com.mixcus.pojo.User">
        select * from mybatis.user where name like concat('%',#{name},'%')
    </select>
    
@Test
public void selectUserByName(){
    //获取session
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    //获取mapper接口的对象
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    List<User> userList = mapper.getUserListByName("张");

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

3.配置解析

3.1、配置文件参数顺序

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:

crtl+单击跳转,没写的跳转官网文档

3.2、properties(属性)标签

如果一个属性在不只一个地方进行了配置,那么,MyBatis 将按照下面的顺序来加载:

  • 首先读取在 properties 元素体内指定的属性。
  • 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
  • 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。

config.properties

driver = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=true

mybatis-config.xml

<properties resource="config.properties">
    <!--可以设置部分参数-->
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</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>

3.3、settings(设置)

  • 懒加载

  • 缓存开启关闭

  • 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。

SLF4J 
LOG4J2 
JDK_LOGGING
COMMONS_LOGGING
STDOUT_LOGGING			(标准日志输出)
NO_LOGGING
<settings>
    <!--标准日志输出-->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
    <!--开启数据库驼峰转换为java字段-->
    <setting name="mapUnderscoreToCamelCase" value="true"/>		<!--默认关闭:false-->
</settings>

3.4、typeAliases(类型别名)

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

对类取别名

<typeAliases>
    <!--为实体类User取别名-->
    <typeAlias type="com.mixcus.pojo.User" alias="User" />
    
</typeAliases>

对应mapper.xml修改

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

该包路径下的类取别名

每一个在包com.mixcus.pojo中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 com.mixcus.pojo.User 的别名为 user;若有注解,则别名为其注解值

<typeAliases>
    <!--该包路径下的类取别名,默认为类名首字母小写-->
    <package name="com.mixcus.pojo"/>
</typeAliases>

对应mapper.xml修改

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

注解使用:

//任何resultType = hello 类型都是User
@Alias("hello")
public class User {

    private int id ;

    private String name ;

    private String password ;
    
    getter,setter
}

对应xml修改

<!--查-->
<select id="getUserList" resultType="hello">
    select * from mybatis.user
</select>

3.5、environments(环境)

MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。

不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推。

每个数据库对应一个 SqlSessionFactory 实例

为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可

<!--修改default的值到达修改环境的效果-->
<environments default="development">
    
    <!--可有多套environment-->
    <environment id="development">
        <!--事务管理-->
        <transactionManager type="JDBC"/>
        
        <!--数据源  POOLED:池-->
        <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>
    
     <environment id="test">
        <!--事务管理-->
        <transactionManager type="JDBC"/>
        
        <!--数据源  POOLED:池-->
        <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>

注意一些关键点:

  • 默认使用的环境 ID(比如:default=“development”)。
  • 每个 environment 元素定义的环境 ID(比如:id=“development”)。
  • 事务管理器的配置(比如:type=“JDBC”)。
  • 数据源的配置(比如:type=“POOLED”)。

默认环境和环境 ID 顾名思义。 环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID。

数据源(dataSource)与事务管理器(transactionManager)

官方文档使用:

数据源(dataSource)与事务管理器(transactionManager)

3.6、mappers(映射器)

  • 映射器 : 定义映射SQL语句文件

MyBatis 到哪里去找映射文件的方式:

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
  • 使用映射器接口实现类的完全限定类名
  • 注:需要配置文件名称和接口名称一致,并且位于同一目录下
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
  • 将包内的映射器接口实现全部注册为映射器
  • 注:但是需要配置文件名称和接口名称一致,并且位于同一目录下
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

4.作用域(scope)与生命周期

4.1、Mybatis的执行过程:

图片

  • 详细执行过程
  • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.2、作用域理解

  • SqlSessionFactoryBuilder 的作用在于创建 SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder 就失去了作用,所以它只能存在于创建 SqlSessionFactory 的方法中,而不要让其长期存在。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
  • SqlSessionFactory 可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象
  • 因为 MyBatis 的本质就是 Java 对数据库的操作,所以 SqlSessionFactory 的生命周期存在于整个 MyBatis 的应用之中,所以一旦创建了 SqlSessionFactory,就要长期保存它,直至不再使用 MyBatis 应用,所以可以认为 SqlSessionFactory 的生命周期就等同于 MyBatis 的应用周期。
  • 由于 SqlSessionFactory 是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建多个 SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导致数据库连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的情况。
  • 因此在一般的应用中我们往往希望 SqlSessionFactory 作为一个单例,让它在应用中被共享。所以说 SqlSessionFactory 的最佳作用域是应用作用域。
  • 如果说 SqlSessionFactory 相当于数据库连接池,那么 SqlSession 就相当于一个数据库连接(Connection 对象),你可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback 等方法,提交或者回滚事务。所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给 SqlSessionFactory,否则数据库资源就很快被耗费精光,系统就会瘫痪,所以用 try…catch…finally… 语句来保证其正确关闭。
  • 所以 SqlSession 的最佳的作用域是请求或方法作用域

注:SqlSession在使用完后需要关闭

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5.ResultMap

  • 解决java实体类的属性与数据库字段不一致的情况

数据库:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

实体类:

public class User {

    private int id ;

    private String name ;

    private String pwd;
}

mapper.xml配置:

<!--查-->
<select id="getUserById" resultType="user">
    select  id,name,password from mybatis.user where id = #{id}
</select>

此时执行sql语句结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

解决方案一:

​ 通过给sql字段取别名

<!--查-->
<select id="getUserById" resultType="user">
    select  id,name,password as pwd from mybatis.user where id = #{id}
</select>

解决方法二:

​ 通过resultMap

<resultMap id="User" type="user">
    <!--
	所写的属性均为type的属性
	property:实体类的属性名
    column:数据库字段名
    -->
    <result property="pwd" column="password" />
</resultMap>

注:字段与属性相同可以忽略不写

6.分页

思考:为什么需要分页?

​ 在学习mybatis等持久层框架的时候,会经常对数据进行增删改查操作,使用最多的是对数据库进行查询操作,如果查询大量数据的时候,我们往往使用分页进行查询,也就是每次处理小部分数据,这样对数据库压力就在可控范围内。

使用Limit实现分页

#语法
SELECT * FROM table LIMIT stratIndex,pageSize

SELECT * FROM table LIMIT 5,10; // 检索记录行 6-15  

#为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1:   
SELECT * FROM table LIMIT 95,-1; // 检索记录行 96-last.  

#如果只给定一个参数,它表示返回最大的记录行数目:   
SELECT * FROM table LIMIT 5; //检索前 5 个记录行  

#换句话说,LIMIT n 等价于 LIMIT 0,n。 

步骤:

1、修改Mapper文件

<select id="selectUser" parameterType="map" resultType="user">
	<!--
	startIndex:起始行号(从0开始,代表第一行)
	pageSize:页面大小数量
	-->
    select * from user limit #{startIndex},#{pageSize}
</select>

2、Mapper接口,参数为map

//选择全部用户实现分页
List<User> selectUser(Map<String,Integer> map);

3、在测试类中传入参数测试

  • 推断:起始位置 = (当前页面 - 1 ) * 页面大小
//分页查询 , 两个参数startIndex , pageSize
@Test
public void testSelectUser() {
   SqlSession session = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);

   int currentPage = 1;  //第几页
   int pageSize = 2;  //每页显示几个
   Map<String,Integer> map = new HashMap<String,Integer>();
   map.put("startIndex",(currentPage-1)*pageSize);
   map.put("pageSize",pageSize);

   List<User> users = mapper.selectUser(map);

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

   session.close();
}

分页插件:PageHelper

官方文档:https://pagehelper.github.io/

7.日志

<settings>
    <!--标准日志输出-->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

8.使用注解进行开发

8.1、sql类型注解

注意:利用注解开发就不需要mapper.xml映射文件了 ,同样需要注册mapper

  • @Select
//查询所有用户
@Select("select * from mybatis.user")
public List<User> getUserList();
  • @Insert
//添加用户,需要提交事务
@Insert("insert into mybatis.user (id,name,password) values (#{id},#{name},#{password})")
public boolean addUser(User user);

  • @Delete
//通过id修改用户信息,需要提交事务
@Delete("delete from mybatis.user where id = #{id}")
public boolean deleteById(int id);
  • @Update
//通过id删除用户,需要提交事务
@Update("update mybatis.user set name = #{name},password = #{password} where id = #{id}")
public boolean updateById(User user);

对MybatisUtils工具类进行优化

//通过工厂类对象获取SqlSession对象
public static SqlSession getSqlSession(){
    //设置为默认打开自动提交事务
    return sqlSessionFactory.openSession(true);
}

//重载
public static SqlSession getSqlSession(boolean flag){
    //选择是否开启自动提交事务
    //true:打开 false:关闭
    return sqlSessionFactory.openSession(flag);
}

8.2、参数注解@Parm()

@Param注解用于给方法参数起一个名字。以下是总结的使用原则:

  • 在方法只接受一个参数的情况下,可以不使用@Param。
  • 在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。
  • 如果参数是 JavaBean , 则不能使用@Param。
  • 不使用@Param注解时,参数只能有一个,并且是Javabean。

@Param的作用就是给参数命名

​ 比如在mapper里面某方法A(int id),当添加注解后A(@Param(“userId”) int id),也就是说外部想要取出传入的id值,只需要取它的参数名userId就可以了。将参数值传如SQL语句中,通过#{userId}进行取值给SQL的参数赋值

  • @Param注解普通基本类型参数
// userid = id
@Delete("delete from mybatis.user where id = #{userid}")
public boolean deleteById(@Param("userid") int id);
  • @Parma注解javabean(java实体类)
//此时temp就是一个实体类的对象,可以通过temp取属性值
@Update("update mybatis.user set name = #{temp.name},password = #{temp.password} where id = #{temp.id}")
public boolean updateById(@Param("temp") User user);

注:

  • 当使用了@Param注解来声明参数的时候,SQL语句取值使用#{},${}取值都可以。

  • 当不使用@Param注解声明参数的时候,必须使用的是#{}来取参数。使用${}方式取值会报错。

8.3、Lombok

Lombok 是一个 Java 库,它通过注解自动生成样板代码,从而简化了 Java 类的编写过程。使用 Lombok,你可以通过简单的注解来自动生成 getter、setter、构造函数、equals、hashCode 等常见的样板代码,减少了重复劳动和冗长的代码。

Lombok 提供了一系列的注解,每个注解对应一个特定的功能。以下是一些常用的 Lombok 注解:

  • @Getter 和 @Setter:自动生成属性的 getter 和 setter 方法
  • @ToString:自动生成 toString() 方法
  • @EqualsAndHashCode:自动生成 equals()hashCode() 方法。
  • @NoArgsConstructor:自动生成无参构造函数
  • @AllArgsConstructor:自动生成包含所有参数的构造函数
  • @Data:自动生成 getter、setter、equals、hashCode 和 toString() 方法。
  • @Builder:使用构建者模式自动生成构造对象的代码。

1.导入lombok的jar包

<!--导入lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.26</version>
</dependency>

2.在实体类中加入注解

@Data
public class Student {

    private int id;

    private String name;

    private Teacher teacher;

}

structure:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

9.一对多及多对一处理

9.1、多对一

多对一的理解:

  • 多个学生对应一个老师
  • 如果对于学生这边,就是一个多对一的现象,即从学生这边关联一个老师!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Student类:

@Data //GET,SET,ToString,有参,无参构造
public class Student {

    private int id;

    private String name;

    private Teacher teacher;

}

teacher类:

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

}

需求:获取所有学生及对应老师的信息
思路:
1. 获取所有学生的信息
2. 根据获取的学生信息的老师ID->获取该老师的信息
3. 思考问题,这样学生的结果集中应该包含老师,该如何处理呢,数据库中我们一般使用关联查询?
1. 做一个结果集映射:StudentTeacher
2. StudentTeacher结果集的类型为 Student
3. 学生中老师的属性为teacher,对应数据库中为tid。
4. 多个 [1,…)学生关联一个老师=> 一对一,一对多
5. 查看官网找到:association – 一个复杂类型的关联;使用它来处理关联查询

StudentMapper:

public interface StudentMapper {
    //使用联表查询
    public List<Student> getStudent1();

    //使用嵌套查询
    public List<Student> getStudent2();
}

注册mapper:

<mappers>
    <mapper class="com.mixcus.mapper.StudentMapper"/>
    <mapper class="com.mixcus.mapper.TeacherMapper"/>
</mappers>

方法一:使用联表查询

<resultMap id="Student" type="student">
    <id property="id" column="sid"/>
    <result property="name" column="sname"/>
    <!--
        javaType 为属性类型,
        此处使用了别名,全名为类路径:com.mixcus.pojo.Teacher
    -->
    <association property="teacher" javaType="teacher">
        <!--
		property="name"  column="tname" :将tname的值赋值给teacher的name属性
		-->
        <result property="name" column="tname" />
    </association>

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

方法二:使用嵌套查询

类似sql子查询

<resultMap id="student2" type="student">
    <!--
		javaType 为属性类型,
        此处使用了别名,全名为类路径:com.mixcus.pojo.Teacher
        column="tid": tid = student.tid
        tid在赋值后又被传入select="getTeacher"查询中作为参数
    -->
    <association property="teacher" javaType="teacher" column="tid"
                 select="getTeacher"/>

</resultMap>

<select id="getStudent2" resultMap="student2">
    select * from mybatis.student
</select>

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

测试:

@Test
public void getStudent(){

    SqlSession sqlSession = MybatisUtils.getSqlSession(true);

    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);

    List<Student> student = mapper.getStudent2();

    for (Student student1 : student) {
        System.out.println(
            "学生名:"+ student1.getName()
            +"\t老师:"+student1.getTeacher().getName());
    }

    sqlSession.close();
}

9.2、一对多

一对多的理解:

  • 一个老师拥有多个学生
  • 如果对于老师这边,就是一个一对多的现象,即从一个老师下面拥有一群学生(集合)!

student类:

@Data
public class Student {
    
    private int id
        
    private String name;
    
    private int tid;//老师编号
}

teacher类:

@Data
public class Teacher {
    
    private int id;//老师编号
    
    private String name;
    //一个老师多个学生
    private List<Student> students;
}

需求:获取指定老师及其所教的学生的信息

TeacherMapper接口

public interface TeacherMapper {

    //获取指定老师,及老师下的所有学生的信息
    //参数老师id
    public Teacher getTeacher(int id);
    
    //嵌套查询
    public Teacher getTeacher1(int id);
}

注册mapper

<mappers>
    <mapper class="com.mixcus.mapper.StudentMapper"/>
    <mapper class="com.mixcus.mapper.TeacherMapper"/>
</mappers>

方法一:联表查询

<mapper namespace="com.mixcus.mapper.TeacherMapper">
    <!--联表查询-->
    <select id="getTeacher" parameterType="int" resultMap="Teacher">
        select t.id,t.name tname,s.id sid,s.name sname
        from mybatis.student s,mybatis.teacher t
        where t.id = s.tid and t.id = #{id}
    </select>
    
    <resultMap id="Teacher" type="teacher">
        <id property="id" column="id"/>
        <result property="name" column="tname"/>
        
        <!--由于teacher的student属性为集合采用collection标签-->
        <collection property="students" ofType="student">
            <!--ofType="" 泛型的类型-->
            <!--写student类的属性映射-->
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="tid" column="id"/>
        </collection>
        
    </resultMap>
    
</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>

测试:

@Test
public void getTeacher(){

    SqlSession sqlSession = MybatisUtils.getSqlSession();

    TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);

    //获取id为1的老师信息及其学生信息
    Teacher teacher = mapper.getTeacher(1);

    System.out.println(teacher.getId());
    System.out.println(teacher.getName());
    System.out.println(teacher.getStudents());

    sqlSession.close();
}

总结:

1、关联-association

2、集合-collection

3、所以association是用于一对一和多对一,而collection是用于一对多的关系

4、JavaType和ofType都是用来指定对象类型的

  • JavaType是用来指定pojo中属性的类型
  • ofType指定的是映射到list集合属性中pojo的类型。

注意说明:

1、保证SQL的可读性,尽量通俗易懂

2、根据实际要求,尽量编写性能更高的SQL语句

3、注意属性名和字段不一致的问题

4、注意一对多和多对一 中:字段和属性对应的问题

5、尽量使用Log4j,通过日志来查看自己的错误

10.动态sql

介绍:

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

动态 SQL 是 MyBatis 的强大特性之一

所用到的标签:

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

10.1、if标签

静态的通过单个条件查询:

<select id="getUserById" parameterType="String" resultType="user">
    select * from mybatis.user where name = #{name}
</select>

使用动态sql进行优化:

<!--利用map进行传参数-->
<select id="getUserByName" parameterType="map" resultType="user">
    select * from mybatis.user where 1 = 1
    <!--if判断-->
    <if test="name!=null">
        and name like concat('%',#{name},'%')
    </if>
    <if test="age!=null">
        and age > #{age}
    </if>
</select>

输入参数为空则执行:

select * from mybatis.user where 1=1

输出所有结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

姓名参数输入,年龄为空:

select * from mybatis.user where 1= 1 and name like "%张%"

输出部分结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

年龄参数输入,姓名为空:

select * from mybatis.user where 1=1 and age > 20

结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

两个参数都不为空

select * from mybatis.user where 1= 1 and name like "%张%" and age > 20

结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

10.2、where标签

  • 使用where标签可以去掉 1= 1

where标签介绍

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

  • 除了第一个if中不需要加and 后续if标签都要加上 and

优化if标签中的代码

<select id="getUserByName" parameterType="map" resultType="user">
    select * from mybatis.user
    <where>
        <!--if判断-->
        <if test="name!=null">
            name like concat('%',#{name},'%')
        </if>
        <if test="age!=null">
            and age > #{age}
        </if>
    </where>
</select>

10.3、choose标签

  • 只会选择一个

  • 多个when标签时,从第一个开始匹配,有匹配成功则直接返回

<select id="getUserByName" parameterType="map" resultType="user">
    select * from mybatis.user
    <where>
        <choose>
            <when test="name!=null">
                name like concat('%',#{name},'%')
            </when>
            <when test="age!=null">
                age > #{20}
            </when>
            <otherwise>
                1=1
            </otherwise>
        </choose>
    </where>

</select>

输入两个非空参数时:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

不输入参数会走

<otherwise>
    <!--语句-->
    1=1
</otherwise>

例:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

10.4、foreach标签

  • 当查询几条记录时可以使用

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)

需求:查询用户为 1、4的用户信息

<select id="getUserByName" parameterType="map" resultType="user">
    select * from mybatis.user
    <!--该collection为集合,可以有map传入-->
    <!--
        collection:指定输入对象中的集合属性
        item:每次遍历生成的对象
        open:开始遍历时的拼接字符串
        close:结束时拼接的字符串
        separator:遍历对象之间需要拼接的字符串
        将item拼接到字符串中 即:and (item = #{item},...)
        select * from user where 1=1 and (id=1 or id=2 or id=3)
     -->
    <where>
        <foreach collection="list" item="id" open="and (" close=")"
                 separator="or">
            id = #{id}
        </foreach>
    </where>
</select>

测试:

@Test
public void getUserById(){

    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    Map<String,Object> map = new HashMap<>();

    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(4);
    map.put("list",list);


    List<User> users = mapper.getUserByName(map);

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

    sqlSession.close();

}

结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

10.5、set标签

set 元素可以用于动态包含需要更新的列,忽略其它不更新的列

set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)

注:至少需满足一点,否则报错

需求:通过输入的参数判断那些信息需要修改

<update id="updateUser" parameterType="map">
    update mybatis.user
    <set>
        <if test="name!=null">
            name = #{name},
        </if>
        <if test="password!=null">
            password = #{password},
        </if>
        <if test="age!=null">
            age = #{age},
        </if>
    </set>
    where id = #{id}
</update>

通过输入的参数确定那些值要修改

10.6、sql片段

有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。

提取SQL片段:

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

引用SQL片段:

<select id="queryBlogIf" parameterType="map" resultType="blog">
  select * from blog
   <where>
       <!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace -->
       <include refid="if-title-author"></include>
       <!-- 在这里还可以引用其他的 sql 片段 -->
   </where>
</select>

注意:

①、最好基于 单表来定义 sql 片段,提高片段的可重用性

②、在 sql 片段中不要包括 where

11.缓存(cache)

  • 简介

    1、什么是缓存 [ Cache ]?

    • 存在内存中的临时数据。

    • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

    2、为什么使用缓存?

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

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

    • 经常查询并且不经常改变的数据。

11.1、Mybatis缓存

Mybatis缓存

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

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

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

注:增删改都是对数据库的数据进行操作,每次执行都需要刷新缓存

11.2、一级缓存

一级缓存也叫本地缓存:

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

每个sqlSession中的缓存相互独立

测试:

UserMapper

public interface UserMapper {

    @Select("select * from user where id = 1")
    public User getUserById();
}

测试:

@Test
public void testFirstCache(){

    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    User user = mapper.getUserById();

    System.out.println(user);

    User user1 = mapper.getUserById();

    System.out.println(user);

    System.out.println(user==user1);

    sqlSession.close();

}

结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它;

一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次

不同sqlsession对象执行相同语句会发送两条sql语句

@Test
public void testFirstCache(){

    SqlSession sqlSession1 = MybatisUtils.getSqlSession();

    SqlSession sqlSession2 = MybatisUtils.getSqlSession();

    UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);

    User user = mapper1.getUserById();

    System.out.println(user);

    User user1 = mapper2.getUserById();

    System.out.println(user);

    System.out.println(user==user1);

    sqlSession1.close();
    sqlSession2.close();

}

结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

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

11.3、二级缓存

  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存

  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;

  • 工作机制

    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
    • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
    • 新的会话查询信息,就可以从二级缓存中获取内容;
    • 不同的mapper查出的数据会放在自己对应的缓存(map)中;

使用步骤

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

<setting name="cacheEnabled" value="true"/>

2、去每个mapper.xml中配置使用二级缓存,这个配置非常简单;【xxxMapper.xml】

<cache/>

官方示例=====>查看官方文档
<cache
 eviction="FIFO"
 flushInterval="60000"
 size="512"
 readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

3、代码测试

  • 所有的实体类先实现序列化接口
  • 测试代码
@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);
   session.close();

   User user2 = mapper2.queryUserById(1);
   System.out.println(user2);
   System.out.println(user==user2);

   session2.close();
}

结论

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

原理图:

图片

  • 26
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值