MyBatis教程

一、了解MyBatis

1、历史(百度百科)

  • MyBatis 本是apache的一个开源项目【iBatis】, 2010年这个项目由apache software foundation(Apache软件基金会) 迁移到了google code(谷歌的代码托管平台),并且改名为MyBatis ,2013年11月迁移到Github。

2、作用 (百度百科)

  • MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

3、说说持久化

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

  1. 程序产生的数据首先都是在内存。
  2. 内存是不可靠的,他丫的一断电数据就没了。
  3. 那可靠的存储地方是哪里?硬盘、U盘、光盘等。
  4. 我们的程序在运行时说的持久化通常就是指将内存的数据存在硬盘。

4、说说持久层

其实分层的概念已经谈到过:

  • 业务是需要操作数据的
  • 数据是在磁盘上的
  • 具体业务调用具体的数据库操作,耦合度太高,复用性太差
  • 将操作数据库的代码统一抽离出来,自然就形成了介于业务层和数据库中间的独立的层

5、聊聊ORM

​ ORM,即Object-Relational Mapping(对象关系映射),它的作用是在关系型数据库和业务实体对象之间作一个映射,这样,我们在具体的操作业务对象的时候,就不需要再去和复杂的SQL语句打交道,只需简单的操作对象的属性和方法。

  • jpa(Java Persistence API)是java持久化规范,是orm框架的标准,主流orm框架都实现了这个标准。
  • hibernate:全自动的框架,强大、复杂、笨重、学习成本较高,不够灵活,实现了jpa规范。Java Persistence API(Java 持久层 API)
  • **MyBatis:**半自动的框架(懂数据库的人 才能操作) 必须要自己写sql,不是依照的jpa规范实现的。

很多人青睐 MyBatis ,原因是其提供了便利的 SQL 操作,自由度高,封装性好…… JPA对复杂 SQL 的支持不好,没有实体关联的两个表要做 join ,的确要花不少功夫。

6、MyBatis的优点和缺点

  • sql语句与代码分离,存放于xml配置文件中:
    • 优点:便于维护管理,不用在java代码中找这些语句;
    • 缺点: JDBC方式可以用打断点的方式调试,但是MyBatis调试比较复杂,一般要通过log4j日志输出日志信息帮助调试,然后在配置文件中修改。
  • 用逻辑标签控制动态SQL的拼接:
    • 优点:用标签代替编写逻辑代码;
    • 缺点:拼接复杂SQL语句时,没有代码灵活,拼写比较复杂。不要使用变通的手段来应对这种复杂的语句。
  • 查询的结果集与java对象自动映射:
    • 优点:保证名称相同,配置好映射关系即可自动映射或者,不配置映射关系,通过配置列名=字段名也可完成自动映射。
    • 缺点:对开发人员所写的SQL依赖很强。
  • 编写原生SQL:
    • 优点:接近JDBC,比较灵活。
    • 缺点:对SQL语句依赖程度很高;并且属于半自动,数据库移植比较麻烦,比如MySQL数据库编程Oracle数据库,部分的SQL语句需要调整。

二 搭个环境

1、建立数据库

CREATE DATABASE `ssm`;
USE `ssm`;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(20) NOT NULL,
`username` varchar(30) DEFAULT NULL,
`password` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert  into `user`(`id`,`username`,`password`) values (1,'itnanls','123456'),(2,'itlils','abcdef'),(3,'ydlclass','987654');

2、构建一个父工程

尝试学习聚合工程的规范,当然可以搭建独立的工程,我们选择一个比较新的jdk版本11:

image-20211020111952686
image-20211020112150144

将父工程的打包方式修改成pom,表示一个聚合工程:

<packaging>pom</packaging>

3、父工程的maven配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.ydlclass</groupId>
    <artifactId>ssm</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <!-- 父模块用于约束版本信息 -->
    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <junit.version>4.13.1</junit.version>
        <mybatis.version>3.5.7</mybatis.version>
        <mysql-connector-java.version>8.0.26</mysql-connector-java.version>
        <lombok.version>1.18.22</lombok.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- 单元测试 -->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>${junit.version}</version>
                <scope>test</scope>
            </dependency>
            <!-- mybatis 核心 -->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>${mybatis.version}</version>
            </dependency>
            <!-- 数据库确定 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql-connector-java.version}</version>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
                <scope>provided</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

4、创建子模块

image-20211020112848952

pom

<properties>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>
</dependencies>

<!-- 处理资源被过滤问题 -->
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.1</version>
            <configuration>
                <source>${maven.compiler.target}</source> <!-- 源代码使用的JDK版本 -->
                <target>${maven.compiler.target}</target> <!-- 需要生成的目标class文件的编译版本 -->
                <encoding>UTF-8</encoding><!-- 字符集编码 -->
            </configuration>
        </plugin>
    </plugins>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>false</filtering>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>false</filtering>
        </resource>
    </resources>
</build>

5、回顾我们的jdbc代码

@Test
public void testConnection1() throws Exception{
    //1.数据库连接的4个基本要素:
    String url = "jdbc:mysql://localhost:3306/ssm?characterEncoding=utf8&serverTimezone=Asia/Shanghai";
    String username = "root";
    String password = "root";
    //8.0之后名字改了  com.mysql.cj.jdbc.Driver
    String driverName = "com.mysql.cj.jdbc.Driver";

    //2.实例化Driver
    Class clazz = Class.forName(driverName);
    Driver driver = (Driver) clazz.newInstance();
    //3.注册驱动
    DriverManager.registerDriver(driver);
    //4.获取连接
    Connection conn = DriverManager.getConnection(url, username, password);

    PreparedStatement preparedStatement = conn.prepareStatement("select * from user where id = ?");
    preparedStatement.setInt(1,1);
    ResultSet resultSet = preparedStatement.executeQuery();

    // 处理结果集
    while (resultSet.next()){
        User user = new User();
        user.setId(resultSet.getInt("id"));
        user.setUsername(resultSet.getString("username"));
        user.setPassword(resultSet.getString("password"));
        System.out.println(user);
    }
}

6、编写MyBatis核心配置文件,mybatis-config.xml

有兴趣的自行深入研究。

  1. UNPOOLED:不使用连接池的数据源
  2. POOLED:使用连接池的数据源
  3. JNDI:使用JNDI实现的数据源,我们在学习JavaEE的时候学习过了

配置文件我们从官网复制:https://mybatis.org/mybatis-3/zh/getting-started.html

<?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>
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/ssm?characterEncoding=utf8&amp;serverTimezone=Asia/Shanghai"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </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>
</configuration>

7、编写实体类

lombok

平时的工作中写setter和getter以及toString方法是不是已经烦了,每次添加一个字段都要重新添加这些方法。

今天我们学习一个神器,从此再也不用写这些重复的代码了,它们在编译的时候动态的帮我们生成这些代码。

  1. javac对源代码进行分析,生成了一棵抽象语法树(AST)
  2. 运行过程中调用实现了“JSR 269 API”的Lombok程序
  3. 此时Lombok就对第一步骤得到的AST进行处理,找到@Data注解所在类对应的语法树(AST),然后修改该语法树(AST),增加getter和setter方法定义的相应树节点
  4. javac使用修改后的抽象语法树(AST)生成字节码文件,即给class增加新的节点(代码块)

1,首先,我们必须安装一个插件,否则编译的时候会报错,你没有写setter方法,又去调用它当然不能编译:

image-20211020113926489

2、引入依赖,lombok在编译的时候,会根据我们的注解动态生成我们需要的构造方法,setter和getter等,运行的时候就没用了。所以scope选择provided。

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.16</version>
    <scope>provided</scope>
</dependency>

从今往后,只需要在对应的类上加上这几个注解,就能完成对应的编译工作

  • @AllArgsConstructor:生成全参构造器。
  • @NoArgsConstructor:生成无参构造器。
  • @Getter/@Setter: 作用类上,生成所有成员变量的getter/setter方法;作用于成员变量上,生成该成员变量的getter/setter方法。可以设定访问权限及是否懒加载等。
  • @Data:作用于类上,是以下注解的集合:@ToString @EqualsAndHashCode @Getter @Setter @RequiredArgsConstructor
  • @Log:作用于类上,生成日志变量。针对不同的日志实现产品,有不同的注解。

注解还有很多,自行学习。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {

    private static final Long serialVersionUID = 1L;

    private int id;
    private String username;
    private String password;
}

此时我们的User是不是变得很简洁呢?

我们随便写一个main方法,然后编译一下:

编译后的结果是这个样子的:

public class User implements Serializable {
    private static final Long serialVersionUID = 1L;
    private int id;
    private String username;
    private String password;

    public static void main(String[] args) {
    }

    public int getId() {
        return this.id;
    }

    public String getUsername() {
        return this.username;
    }

    public String getPassword() {
        return this.password;
    }

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

    public void setUsername(String username) {
        this.username = username;
    }

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

    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (!(o instanceof User)) {
            return false;
        } else {
            User other = (User)o;
            if (!other.canEqual(this)) {
                return false;
            } else if (this.getId() != other.getId()) {
                return false;
            } else {
                Object this$username = this.getUsername();
                Object other$username = other.getUsername();
                if (this$username == null) {
                    if (other$username != null) {
                        return false;
                    }
                } else if (!this$username.equals(other$username)) {
                    return false;
                }

                Object this$password = this.getPassword();
                Object other$password = other.getPassword();
                if (this$password == null) {
                    if (other$password != null) {
                        return false;
                    }
                } else if (!this$password.equals(other$password)) {
                    return false;
                }

                return true;
            }
        }
    }

    protected boolean canEqual(Object other) {
        return other instanceof User;
    }

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        int result = result * 59 + this.getId();
        Object $username = this.getUsername();
        result = result * 59 + ($username == null ? 43 : $username.hashCode());
        Object $password = this.getPassword();
        result = result * 59 + ($password == null ? 43 : $password.hashCode());
        return result;
    }

    public String toString() {
        int var10000 = this.getId();
        return "User(id=" + var10000 + ", username=" + this.getUsername() + ", password=" + this.getPassword() + ")";
    }

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

    public User() {
    }
}

我们发现,编译后注解没了,其他的都有了,自然运行时就能调用了呀!

三、日志配置

配置日志的一个重要原因是想在调试的时候能观察到sql语句的输出,能查看中间过程

1、标准日志实现

指定 MyBatis 应该使用哪个日志记录实现。如果此设置不存在,则会自动发现日志记录实现。

STD:standard out:输出

STDOUT_LOGGING:标准输出日志

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

2、组合logback完成日志功能(扩展)

使用步骤:

1、导入log4j的包

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>${logback.version}</version>
</dependency>

2、配置文件编写 log4j.properties

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss} %c [%thread] %-5level %msg%n"/>
    <property name="log_dir" value="d:/logs" />

    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <target>System.out</target>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>

    <appender name="file" class="ch.qos.logback.core.FileAppender">
        <!--日志格式配置-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <!--日志输出路径-->
        <file>${log_dir}/sql.log</file>
    </appender>

    <root level="ALL">
        <appender-ref ref="console"/>
    </root>

    <logger name="mybatis.sql" level="debug" additivity="false">
        <appender-ref ref="console"/>
        <appender-ref ref="file"/>
    </logger>

</configuration>

3、setting设置日志实现

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

四、CRUD来一套

在这里插入图片描述

1、基本流程:

// 1、创建一个SqlSessionFactory的 建造者 ,用于创建SqlSessionFactory
// SqlSessionFactoryBuilder中有大量的重载的build方法,可以根据不同的入参,进行构建
// 极大的提高了灵活性,此处使用【创建者设计模式】
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// 2、使用builder构建一个sqlSessionFactory,此处我们基于一个xml配置文件
// 此过程会进行xml文件的解析,过程相对比较复杂
SqlSessionFactory sqlSessionFactory = builder.build(Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatis-config.xml"));
// 3、通过sqlSessionFactory获取另一个session,此处使用【工厂设计模式】
SqlSession sqlSession = sqlSessionFactory.openSession();

1、创建一个SqlSessionFactory的 建造者 ,用于创建SqlSessionFactory

SqlSessionFactory sqlSessionFactory = null;


    @Before
    public void before() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = null;
        inputStream = Resources.getResourceAsStream(resource);
        // 1、创建一个SqlSessionFactory的 建造者 ,用于创建SqlSessionFactory
        // SqlSessionFactoryBuilder中有大量的重载的build方法,可以根据不同的入参,进行构建
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

        sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        // 2、使用builder构建一个sqlSessionFactory,此处我们基于一个xml配置文件
        // 此过程会进行xml文件的解析,过程相对比较复杂


    }

    @Test
    public void testSqlSession() {
        log.debug("is [{}]", sqlSessionFactory);

        // 3、通过sqlSessionFactory获取另一个session,此处使用【工厂设计模式】

        try (SqlSession session = sqlSessionFactory.openSession()) 
            List<Object> objects = session.selectList("user.select");
            log.debug("result is{}", objects);

        }
    }
    @Test
    public void testid() {
        try (SqlSession session = sqlSessionFactory.openSession(true)) {
            UserMapper mapper = session.getMapper(UserMapper.class);
            //获得一个代理对象 jdk.proxy 类,  代理对象实现了UserMappe的接口
            //mapper 和xml
            User user = mapper.selectOne(1);
            log.debug("users is [{}]", user);
        }
    }

2、使用builder构建一个sqlSessionFactory,此处我们基于一个xml配置文件

3、通过sqlSessionFactory获取另一个session,此处使用【工厂设计模式】

4、一个sqlsession就是一个会话,可以使用sqlsession对数据库进行操作,原理后边会讲。

其实第一次使用sqlsession我们可能会这么操作:

try (SqlSession sqlSession = sqlSessionFactory.openSession();){
    List<Object> objects = sqlSession.selectList("select * from user");
}

(1)SqlSessionFactory

​ 每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

加载配置文件 建造工厂(一级缓存),工厂中获取产品 sqlsession会话 (二级缓存)

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式。

(2)SqlSession

​ 每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:

try (SqlSession session = sqlSessionFactory.openSession()) {
  // 你的应用逻辑代码
}

在所有代码中都遵循这种使用模式,可以保证所有数据库资源都能被正确地关闭。

(3)测试

private SqlSessionFactory sqlSessionFactory = null;
private static final Logger LOGGER = LoggerFactory.getLogger(TestMybatis.class);

@Before
public void build() {
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    sqlSessionFactory =builder.build(Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatis-config.xml"));
}

当我们看到sqlSession有selectList,delete,update等方法时,我们会忍不住这样去使用。

@Test
public void testSession(){
    try (SqlSession sqlSession = sqlSessionFactory.openSession();){
        List<Object> objects = sqlSession.selectList("select * from user");
        System.out.println(objects);
    }
}

不能直接写sql语句 ,只能通过标识符,加载xml文件中的sql语句

org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for select * from user
### Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for select * from user
321/5000 

###查询数据库错误。原因:java.lang.IllegalArgumentException:映射的语句集合不包含select * from user的值

###原因:java.lang.IllegalArgumentException:映射的语句集合不包含select * from user的值

错误消息中显示Mapped Statements collection does not contain value for select * from user,说是mapper的申明中没有“select * from user”,其实这里让你填的是一个申明。我们在源码注释中可以看到:】

/**
* Retrieve a list of mapped objects from the statement key.
* @param <E> the returned list element type
* @param statement Unique identifier matching the statement to use.//与要使用的语句匹配的唯一标识符。  
* @return List of mapped object
*/
<E> List<E> selectList(String statement);

说明这个还需要通过使用sql的一个标识符。

在MyBatis中我们还需要一个sql的映射文件来给每一个sql语句定义一个唯一标识符,我们起名UserMapper.xml,将这个文件放在resources文件夹下的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="UserMapper">
    <select id="selectOne" resultType="com.ydlclass.User">
        select * from user where id = #{id}
    </select>
</mapper>

还需要讲这个配置文件注册到主配置文件中,在最后边添加如下代码:

<mappers>
    <mapper resource="mapper/UserMapper.xml"/>
</mappers>

我们会发现每一个mapper映射文件都有一个命名空间,从下边的用法来看我们能大致明白命名空间的作用。就如同我们国家可以有相同名字的县,但是加上市以后我们就能很轻松的区别两个县,这个市就是县的命名空间。

try (SqlSession sqlSession = sqlSessionFactory.openSession();){
    List<Object> users = sqlSession.selectList("UserMapper.selectAll");
    LOGGER.debug("result is [{}]",objects);
}


我们得到了正确的结果:

image-20211104145927906

当然不加命名空间也可,因为我们并没有其他重复的标识符,这个selectOne就是这条语句的标识符。

try (SqlSession sqlSession = sqlSessionFactory.openSession();){
    List<Object> users = sqlSession.selectList("selectAll");
    LOGGER.debug("result is [{}]",objects);
}

(4)动态代理实现

  • resultType:指定返回类型,查询是有结果的,结果啥类型,你得告诉我
  • parameterType:指定参数类型,查询是有参数的,参数啥类型,你得告诉我id:指定对应的方法映射关系,就是你得告诉我你这sql对应的是哪个方法
  • #{id}:sql中的变量,要保证大括号的变量必须在User对象里有
  • #{}:占位符,其实就是咱们的【PreparedStatement】处理这个变量,mybatis会将它替换成?
  • Mapper动态代理方式

    Mapper接口开发方法只需要程序员编写Mapper接口(相当于Dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。

    在这里插入图片描述

    Mapper接口开发需要遵循以下规范:
    1、Mapper.xml文件中的namespace与mapper接口的类路径相同。

    <mapper namespace="com.ydlclass.mapper.DeptMapper">


    2、Mapper接口方法名和Mapper.xml中定义的每个statement的id相同

    <select id="selectAll" resultMap="DeptMap">
            select
            id,`name`
            from dept
    </select>
     List<Dept>selectAll(Dept dept);


    3、Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同

    <insert id="insertByIds" parameterType="list">
            insert into `user` (id,user_name,password) values
            <foreach collection="users" item="user" separator=",">
                (#{user.id},#{user.username},#{user.password})
            </foreach>
    </insert>
    int insertByIds(@Param("users") List<User> users);


    4、Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同

    输出结果 就是实体类,名字就是引用

    <select id="selectByUser" resultType="com.ydlclass.entity.User">
    
            select * from user
            <where>
                <if test="id != null">
                    and id=#{id}
                </if>
                <if test="username != null and username!= ''">
                    and user_name=#{username}
                </if>
                <if test="password != null and password != ''">
                    and password =#{password}
                </if>
            </where>
        </select>
    List<User> selectByUser(User user);

    我们可以这样:

    1、定义一个接口:

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

    2、修改映射文件,让命名空间改为接口的权限定名,id改为方法的名字

    <mapper namespace="com.ydlclass.mapper.UserMapper">
        <select id="selectAll" resultType="com.ydlclass.entity.User">
            select * from user
        </select>
    </mapper>
    

    3、我们可以很简单的使用如下的方法操作:

    try (SqlSession sqlSession = sqlSessionFactory.openSession();){
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> list = mapper.selectAll();
        LOGGER.debug("result is [{}]",list);
    }
    

    这样写起来简直不要太舒服,也确实拿到了对应的结果:

    image-20211104145927906

    ​ 这里很明显使用了动态代理的方式,

    sqlSession.getMapper(UserMapper.class)帮我们生成一个代理对象,该对象实现了这个接口的方法然后调用了他的方法

    具体的数据库操作比如建立连接,创建statment等重复性的工作交给框架来处理,唯一需要额外补充的就是sql语句了,xml文件就是在补充这个描述信息,比如具体的sql,返回值的类型等,框架会根据命名空间自动匹配对应的接口,根据id自动匹配接口的方法,不需要我们再做额外的操作。

    接下来我们就把增删改查全部写一下,感受一下:

    2、select(查询)

    select标签是mybatis中最常用的标签

    1、在UserMapper中添加对应方法

    /**
         * 根据id查询用户
         * @param id
         * @return
         */
    User selectUserById(int id);
    

    2、在UserMapper.xml中添加Select语句

    <select id="selectUserById" resultType="com.ydlclass.entity.User"  parameterType="int">
        select id,username,password from user where id = #{id}
    </select>
    

    新的知识点,在映射文件中有一些属性:

    除了#{}还有${},看看有啥区别,面试常问

    • #{} 的作用主要是替换预编译语句(PrepareStatement)中的占位符? 【推荐使用】
      • INSERT INTO user (username) VALUES (#{username}); INSERT INTO user (username) VALUES (?); 1
    • ${} 的作用是直接进行字符串替换
      • INSERT INTO user (username) VALUES ('${username}'); INSERT INTO user (username) VALUES ('楠哥'); 1

    3、测试类中测试

    try (SqlSession sqlSession = sqlSessionFactory.openSession();){
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.selectUserById(1);
        LOGGER.debug("the user is [{}]",user);
    }
    

    结果正确:

    image-20211020153327382

    我们不妨把session的创建定义成一个方法:

    private static SqlSession open(){
        // 1、创建一个SqlSessionFactory的 建造者 ,用于创建SqlSessionFactory
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        // 2、使用builder构建一个sqlSessionFactory,此处我们基于一个xml配置文件
        SqlSessionFactory sqlSessionFactory = builder.build(Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatis-config.xml"));
        // 3、通过sqlSessionFactory获取另一个session,此处使用【工厂设计模式】
        return sqlSessionFactory.openSession();
    }
    

    3、insert(插入)

    insert标签被用作插入操作

    1、接口中添加方法

    /**
         * 新增user
         * @param user
         * @return
         */
    int addUser(User user);
    

    2、xml中加入insert语句

    <insert id="addUser" parameterType="com.ydlclass.entity.User">
        insert into user (id,username,password) values (#{id},#{username},#{password})
    </insert>
    

    3、测试

    @Test
    public void testAdd(){
        SqlSession sqlSession = open();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int rows = mapper.addUser(new User(10, "lucy", "123"));
        LOGGER.debug("Affected rows: [{}]",rows);
    }
    

    返回值是1,你欣喜若狂,总以为就是这么简单,但事实是,数据库压根没有存进去:

    image-20211020155233979

    注:增、删、改操作需要提交事务!在默认情况下MySQL的事务是自动提交的,而框架却默认设置成了手动提交,我们开启了事务,又没有去提交事务,结束后自然会回滚啊:

    第一种方式,在openSession方法传入true,就变成自动提交了:

    sqlSessionFactory.openSession(true);
    

    第二种方式,我们手动提交,事实上我们肯定要手动提交事务:

    @Test
    public void testAdd(){
        SqlSession sqlSession = open();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int rows = mapper.addUser(new User(10, "lucy", "123"));
        LOGGER.debug("Affected rows: [{}]",rows);
        sqlSession.commit();
        sqlSession.close();
    }
    

    思考,如果参数没有传实体类而是传了多个参数,,能不能执行

    比如数据库为id,方式传入userId

    1、在UserMapper中添加对应方法

    /**
         * 新增用户
         * @param id
         * @param name
         * @param pws
         * @return
         */
    int insertUser(int id,String name,String pws);
    

    2、在UserMapper.xml中添加Select语句

    <insert id="insertUser" parameterType="com.ydlclass.entity.User">
        insert into user (id,username,password) values (#{id},#{username},#{password})
    </insert>
    

    3、测试

    @Test
        public void testInsert(){
            SqlSession sqlSession = open();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            int rows = mapper.insertUser(10, "lucy", "123");
            LOGGER.debug("Affected rows: [{}]",rows);
            sqlSession.commit();
            sqlSession.close();
        }
    

    出问题了

     Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [arg2, arg1, arg0, param3, param1, param2]
    

    这就无法映射了。

    这就需要注解@Param了,这其实就是在做映射关系,xml里的${username}和方法中的name做映射:

    多个参数需要 @Param

    @Param("users") List<User> users
    int insertUser(@Param("id") int id, @Param("username") String name,@Param("password") String pws);
    

    此时又一次成功了:

    @Test
    public void testInsert(){
        SqlSession sqlSession = open();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int rows = mapper.insertUser(11, "lucy", "123");
        LOGGER.debug("Affected rows: [{}]",rows);
        sqlSession.commit();
        sqlSession.close();
    }
    

    所以我们遇到mapper中有多个参数时,一定要使用@Param注解,建立映射关系。

    4、update(修改)

    update标签用于更新操作

    1、写接口

    /**
     * 修改用户
     * @param user
     * @return
     */
    int updateUser(User user);
    

    2、写SQL

    <update id="updateUser" parameterType="com.ydlclass.entity.User">
        update user set username=#{username},password=#{password} where id = #{id}
    </update>
    

    3、测试

    @Test
    public void testUpdate(){
        SqlSession sqlSession = open();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int rows = mapper.updateUser(new User(11, "小微微", "123"));
        LOGGER.debug("Affected rows: [{}]",rows);
        sqlSession.commit();
        sqlSession.close();
    }
    

    5、delete(删除)

    delete标签用于做删除操作

    1、写接口

    /**
    * 删除一个用户
    * @param id
    * @return
    */
    int deleteUser(int id);
    

    2、写SQL

    <delete id="deleteUser" parameterType="int">
      delete from user where id = #{id}
    </delete>
    

    3、测试

     @Test
    public void testDeleteUser(){
        SqlSession sqlSession = open();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int affectedRows = mapper.deleteUser(5);
        LOGGER.debug("Affected rows: [{}]",affectedRows);
        sqlSession.commit();
        sqlSession.close();
    }
    

    6、模糊查询

    方案一:在Java代码中拼串

    string name = “%IT%”;
    list<name> names = mapper.getUserByName(name);
    
    <select id=”getUsersByName”>
    	select * from user where name like #{name}
    </select>
    

    方案二:在配置文件中拼接

    string name = “IT”;
    list<User> users = mapper.getUserByName(name);
    
    <select id=”getUsersByName”>
        select * from user where name like "%"#{name}"%"
    </select>
    

    为什么必须用双引号?

    方案三:在配置文件中拼接

    <select id=”getUsersByName”>
        select * from user where name like "%${name}%"
    </select>
    

    方案四:在配置文件中拼接

    
    
    <select id=”getUsersByName”>
        select * from user where name like concat('%', #{username},'%')
    </select>
    
    

    7、map的使用

    map可以代替任何的实体类,所以当我们数据比较复杂时,可以适当考虑使用map来完成相关工作

    1、写sql

    <select id="getUsersByParams" parameterType="java.util.HashMapmap">
        select id,username,password from user where username = #{name}
    </select>
    

    2、写方法

    /**
    * 根据一些参数查询
    * @param map
    * @return
    */
    List<User> getUsersByParams(Map<String,String> map);
    

    3、测试

    @Test
    public void findByParams() {
        UserMapper mapper = session.getMapper(UserMapper.class);
        Map<String,String> map = new HashMap<String, String>();
        map.put("name","磊磊哥");
        List<User> users = mapper.getUsersByParams(map);
        for (User user: users){
            System.out.println(user.getUsername());
        }
    }

    五、使用注解开发

    DROP TABLE IF EXISTS `admin`;
    CREATE TABLE `admin` (
    `id` int(20) NOT NULL,
    `username` varchar(30) DEFAULT NULL,
    `password` varchar(30) DEFAULT NULL,
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    insert  into `admin`(`id`,`username`,`password`) values (1,'itnanls','123456'),(2,'itlils','abcdef'),(3,'小微','987654');
    

    ​ MyBatis最初配置信息是基于 XML ,映射语句(SQL)也是定义在 XML 中的。而到MyBatis 3提供了新的基于注解的配置。不幸的是,Java 注解的的表达力和灵活性十分有限。最强大的 MyBatis 映射并不能用注解来构建,所以这里我们作为了解。

    • sql 类型主要分成 :
    • @select ()
    • @update ()
    • @Insert ()
    • @delete ()

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

    1、接口中添加注解

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Admin {
    
        private static final Long serialVersionUID = 1L;
    
        private int id;
        private String username;
        private String password;
    }
    
    public interface AdminMapper {
    
        /**
         * 保存管理员
         * @param admin
         * @return
         */
        @Insert("insert into admin (username,password) values (#{username},#{password})")
        int saveAdmin(Admin admin);
    
        /**
         * 跟新管理员
         * @param admin
         * @return
         */
        @Update("update admin set username=#{username} , password=#{password} where id = #{id}")
        int updateAdmin(Admin admin);
    
        /**
         * 删除管理员
         * @param admin
         * @return
         */
        @Delete("delete from admin where id=#{id}")
        int deleteAdmin(int id);
    
        /**
         * 根据id查找管理员
         * @param id
         * @return
         */
        @Select("select id,username,password from admin where id=#{id}")
        Admin findAdminById(@Param("id") int id);
    
        /**
         * 查询所有的管理员
         * @return
         */
        @Select("select id,username,password from admin")
        List<Admin> findAllAdmins();
    
    }
    

    2、核心配置文件中配置

    添加一个mapper的配置:

    <mapper class="com.ydlclass.mapper.AdminMapper"/>

    3、进行测试

    public class TestAdminMapper {
    
        @Test
        public void testSaveAdmin() {
            SqlSession session = open();
            AdminMapper mapper = session.getMapper(AdminMapper.class);
            Admin admin = new Admin(1,"微微姐","12345678");
            int affectedRows = mapper.saveAdmin(admin);
             LOGGER.debug("Affected rows: [{}]",affectedRows);
            session.commit();
            session.close();
        }
    
        @Test
        public void testUpdateAdmin() {
            SqlSession session = open();
            AdminMapper mapper = session.getMapper(AdminMapper.class);
            Admin user = new Admin(1,"磊磊哥","12345678");
            int affectedRows = mapper.updateAdmin(user);
            LOGGER.debug("Affected rows: [{}]",affectedRows);
            session.commit();
            session.close();
        }
    
        @Test
        public void testDeleteAdmin(){
            SqlSession session = open();
            AdminMapper mapper = session.getMapper(AdminMapper.class);
            int affectedRows = mapper.deleteAdmin(2);
            LOGGER.debug("Affected rows: [{}]",affectedRows);
            session.commit();
            session.close();
        }
    
        @Test
        public void testGetAdminById(){
            SqlSession session = open();
            AdminMapper mapper = session.getMapper(AdminMapper.class);
            Admin admin = mapper.findAdminById(1);
            LOGGER.debug("The admin is: [{}]",admin);
            session.commit();
            session.close();
        }
    
        @Test
        public void testGetAllAdmins(){
            SqlSession session = open();
            AdminMapper mapper = session.getMapper(AdminMapper.class);
            List<Admin> admins = mapper.findAllAdmins();
            LOGGER.debug("The admins is: [{}]",admin);
            session.commit();
            session.close();
        }
    
        private static SqlSession open(){
            // 1、创建一个SqlSessionFactory的 建造者 ,用于创建SqlSessionFactory
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            // 2、使用builder构建一个sqlSessionFactory,此处我们基于一个xml配置文件
            SqlSessionFactory sqlSessionFactory = builder.build(Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatis-config.xml"));
            // 3、通过sqlSessionFactory获取另一个session,此处使用【工厂设计模式】
            return sqlSessionFactory.openSession();
        }
    
    }