【学习笔记】MyBatis学习笔记

本文是动力节点MyBatis教程的学习笔记。

第一章

1. 三层架构

(1) 三层的功能

  • 表示层(User Interface Layer):接受用户数据,显示请求的处理结果,包括jsp、html、servlet等。对应controller包;

  • 业务逻辑层(Business Logic Layer):接受表示层传递过来的数据,检查数据,计算业务逻辑,调用数据访问层获取数据。对应service包;

  • 数据访问层(Data Access Layer,DAL):也称持久层,与数据库打交道。对应DAO包。

(2) 三层中类的交互

用户使用表示层 --> 业务逻辑层 --> 数据访问层 --> 数据库

(3) 三层对应的处理框架

表示层 ---- servlet ---- SpringMVC框架

业务逻辑层 ---- service类 ---- Spring框架

数据访问层 ---- DAO类 ---- MyBatis框架

2. JDBC编程

(1) JDBC实例

public void findStudent() {
    Connection conn = null;
    Statement stmt = null;
    ResultSet rs = null;
    try { //注册mysql驱动
        Class.forName("com.mysql.jdbc.Driver");
        // 连接数据的基本信息 url ,username,password
        String url = "jdbc:mysql://localhost:3306/springdb";
        String username = "root";
        String password = "123456";
        // 创建连接对象
        conn = DriverManager.getConnection(url, username, password);
        // 保存查询结果
        List<Student> stuList = new ArrayList<>();
        // 创建Statement, 用来执行sql语句 
        stmt = conn.createStatement();
        // 执行查询,创建记录集,
        rs = stmt.executeQuery("select * from student");
        while (rs.next()) {
            Student stu = new Student();
            stu.setId(rs.getInt("id"));
            stu.setName(rs.getString("name"));
            stu.setAge(rs.getInt("age"));
            // 从数据库取出数据转为Student对象,封装到List集合
            stuList.add(stu);
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            //关闭资源 
            if (rs != null) ;
            {
                rs.close();
            }
            if (stmt != null) {
                stmt.close();
            }
            if (conn != null) {
                conn.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

(2) JDBC缺点

  • 代码较多,开发效率低
  • 需要关注Connection、Statement、ResultSet对象的创建和销毁
  • 对ResultSet查询的结果,需要自己封装为List
  • 重复的代码多
  • 业务代码和数据库操作混在一起

3. MyBatis框架概述

(1) MyBatis功能

SQL映射:可以把数据库表中的一行数据,映射为一个java对象,操作这个对象,就相当于操作表中的数据。

数据访问:对数据库执行增删改查

  • 注册数据库的驱动
  • 创建 JDBC中必须使用的Connection、Statement、ResultSet对象
  • 从 xml中获取中获取sql,执行sql语句,把ResultSet结果转换为java对象
  • 关闭资源

(2) 入门案例

项目名为01-helloMyBatis,目录的结构如下:

【学习笔记】MyBatis学习笔记

1) 创建student表并插入数据

在MySQL的mybatis库中创建student表:

CREATE TABLE `student` (
  `id` int NOT NULL,
  `name` varchar(80) NOT NULL,
  `email` varchar(100) NOT NULL,
  `age` int NOT NULL,
  PRIMARY KEY (`id`,`age`)
) ENGINE=InnoDB;


INSERT INTO `student` VALUES ('1001', '李四', 'lisi@qq.com', '11');
INSERT INTO `student` VALUES ('1002', '张三', 'zhangsan@126.com', '12');

2) 加入maven的MyBatis坐标,MySQL驱动的坐标

创建maven工程,在pom.xml文件中添加以下依赖

<!-- mybatis依赖 -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.5</version>
</dependency>

<!-- mysql驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.21</version>
</dependency>

<!-- lombok依赖 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
</dependency>

其中版本号可以不一样,lombok提供的@Data注解可以为实体类添加get、set、toString方法等,以简化代码。

3) 创建实体类Student

package cn.ecnu.domain;

import lombok.Data;

// 推荐和表名一样,方便记忆
@Data
public class Student {
    // 定义属性,目前要求属性名和列名一致
    private Integer id;
    private String name;
    private String email;
    private Integer age;
}

4) 创建持久层的DAO接口,定义操作数据库的方法

package cn.ecnu.dao;

import cn.ecnu.domain.Student;

import java.util.List;

// 接口操作student表
public interface StudentDAO {

    // 查询student表的所有的数据
    public List<Student> selectStudents();
}

5) 创建一个MyBatis使用的配置文件

也叫做SQL映射文件:写sql语句,一般一个表一个sql映射文件,为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="cn.ecnu.dao.StudentDAO">
    <!--
        id:要执行的sql语句的唯一标识,mybatis会使用这个id的值来找到要执行的sql语句,可以自定义,但要求使用接口中的方法名称

        resultType:表示结果类型的,是sql语句执行后得到ResultSet,遍历这个ResultSet得到java对象的类型,值写类型的全限定名称
    -->
    <select id="selectStudents" resultType="cn.ecnu.domain.Student">
        select id,name,email,age from student order by id;
    </select>
</mapper>

<!--
SQL映射文件:写sql语句,mybatis会执行这些sql
1. 指定约束文件
    <!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

    mybatis-3-mapper.dtd是约束文件的名称,扩展名是dtd的

2. 约束文件的作用:限制、检查在当前文件中出现的标签、属性必须符合mybatis的要求

3. mapper是当前文件的根标签,必须的
   namespace:命名空间,唯一值,可以是自定义的字符串。要求使用DAO接口的全限定名称

4. 在当前文件中,可以使用特定的标签,表示数据库的特定操作
    <select>:表示执行查询
    <update>:表示更新数据库的操作,在里面写的是update语句
    <insert>:表示插入,在里面写的是insert语句
    <delete>:表示删除,在里面写的是delete语句
-->

6) 创建MyBatis的主配置文件

一个项目一个主配置文件,主配置文件提供了数据库的链接信息和sql映射文件的位置信息

<?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>
    <!--
        环境配置:数据的链接信息
        default:必须和某个environment的id值一样,用来告诉mybatis使用按个数据库的连接信息,也就访问哪个数据库
        -->
    <environments default="mysql">
        <!--
            environment:一个数据库信息的配置,环境
            id:数据源的名称,一个唯一值,自定义
        -->
        <environment id="mysql">
            <!--
                transationManager:mybatis的事务类型
                    type:JDBC(表示使用jdbc中的Connection对象的commit、rollback做事务处理)
            -->
            <transactionManager type="JDBC"/>
            <!--
                dataSource:数据源,用来链接数据库
                    type: POOLED,表示使用数据库的连接池
            -->
            <dataSource type="POOLED">
                <!--连接数据库的四个要素-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <!-- sql映射文件的位置 -->
    <mappers>
        <!--一个mapper标签指定一个文件的位置,从类路径开始的路径信息-->
        <mapper resource="cn/ecnu/dao/StudentDAO.xml"/>
    </mappers>

</configuration>

<!--
    mybatis的主配置文件:主要定义了数据的配置信息,sql映射文件的位置

    1. 约束文件
        <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">

       mybatis-3-config.dtd:约束文件的名称

    2. configuration:根标签

-->

写好mybatis的配置文件后,resources下面的资源会在maven的compile的时候自动打包到target目录下,但StudentDAO.xml不会自动打包,需要在pom.xml中设置要扫描的配置文件的位置

<build>
    <resources>
        <resource>
            <!-- 所在的目录 -->
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>false</filtering>
        </resource>
    </resources>
</build>

7) 创建使用MyBatis类

通过MyBatis访问数据库

package cn.ecnu;

import cn.ecnu.domain.Student;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class MyApp {

    public static void main(String[] args) throws IOException {
        // 访问mybatis读取student数据
        // 1. 定义mybatis主配置文件的名称,从类路径的根开始(target/classes)
        String config="mybatis.xml";
        // 2. 读取config表示的文件
        InputStream in = Resources.getResourceAsStream(config);
        // 3. 创建SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
        // 4. 创建SqlSessionFactory对象
        SqlSessionFactory factory=builder.build(in);
        // 5. 【重要】获取SqlSession对象,从SqlSessionFactory中获取SqlSession
        SqlSession sqlSession = factory.openSession();
        // 6. 【重要】指定要执行的sql语句的标识,sql映射文件中的namespace + "." + 标签的id值
        String sqlId="cn.ecnu.dao.StudentDAO"+"."+"selectStudents";
        // 7. 执行sql语句,通过sqlId找到语句
        List<Student> studentList=sqlSession.selectList(sqlId);
        // 8. 输出结果
        studentList.forEach(System.out::println);
        // 9. 关闭SqlSession对象
        sqlSession.close();
    }
}

运行得到的输出结果就是数据库中插入的两条数据。

(3) 插入数据

在StudentDAO.java接口中添加插入对应的方法:

// 插入方法
/*
* 参数:student为插入的数据
* 返回值:插入的记录数
* */
public int insertStudent(Student student);

在StudentDAO.xml中添加插入方法对应的sql语句:

<insert id="insertStudent">
    insert into student values(#{id},#{name},#{email},#{age});
</insert>

其中#{属性名}是从insertStudent方法的入参student类中取得的属性值。

MyApp.java的6、7、8步需要修改,其他不变:

// 6. 【重要】指定要执行的sql语句的标识,sql映射文件中的namespace + "." + 标签的id值
String sqlId = "cn.ecnu.dao.StudentDAO" + "." + "insertStudent";
// 7. 执行sql语句,通过sqlId找到语句
Student student = new Student();
student.setId(1003);
student.setName("张飞");
student.setEmail("zhangfei@sina.com");
student.setAge(21);
int num = sqlSession.insert(sqlId, student);
// mybatis默认是不自动提交事务的,所以insert、update、delete后要手动提交事务
sqlSession.commit();
// 8. 输出结果
System.out.println("执行insert的结果: " + num);

第二章

1. 主要类的介绍

(1) Resources

mybatis中的一个类,负责读取主配置文件

InputStream in = Resources.getResourceAsStream(config);

(2) SqlSessionFactoryBuilder

创建SqlSessionFactory对象

SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
SqlSessionFactory factory=builder.build(in);

(3) SqlSessionFactory

重量级对象,程序创建一个对象耗时较长,使用资源比较多。在整个项目中,有一个就够用了

SqlSessionFactory接口的实现类为DefaultSqlSessionFactory

SqlSessionFactory作用:获取SqlSession对象

SqlSession sqlSession = factory.openSession();

openSession()方法说明:

  • openSession():无参数的,获取非自动提交事务的SqlSession对象
  • openSession(boolean):当入参为true时,获取自动提交事务的SqlSession,反之为非自动提交事务的

(4) SqlSession

SqlSession接口:定义了操作数据的方法,例如selectOne()、selectList()、insert()、update()、delete()、commit()、rollback()等

SqlSession接口的实现类为DefaultSqlSession

使用需求:SqlSession对象不是线程安全的,需要在方法内部使用,在执行sql语句之前,使用openSession()获取SqlSession对象,在执行完sql语句之后,需要执行SqlSession.close()来关闭它,这样能保证它的使用是线程安全的

2. 传统DAO使用方式

为StudentDAO接口增加对应的实现类,把业务逻辑代码放到实现类里,而不是直接放到main方法或者单元测试方法中。项目名为02-MyBatisUtils,代码组织结构如下:

【学习笔记】MyBatis学习笔记

实现类文件StudentDAOImpl.java中的内容为:

package cn.ecnu.dao.impl;

import cn.ecnu.dao.StudentDAO;
import cn.ecnu.domain.Student;
import cn.ecnu.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;

import java.util.List;

public class StudentDAOImpl implements StudentDAO {

    @Override
    public List<Student> selectStudents() {
        // 获取SqlSession
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        //【重要】指定要执行的sql语句的标识,sql映射文件中的namespace + "." + 标签的id值
        String sqlId = "cn.ecnu.dao.StudentDAO" + "." + "selectStudents";
        // 执行sql语句,通过sqlId找到语句
        List<Student> studentList = sqlSession.selectList(sqlId);
        // 关闭SqlSession对象
        sqlSession.close();
        return studentList;
    }

    @Override
    public int insertStudent(Student student) {
        // 获取SqlSession
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        //【重要】指定要执行的sql语句的标识,sql映射文件中的namespace + "." + 标签的id值
        String sqlId = "cn.ecnu.dao.StudentDAO" + "." + "insertStudent";
        // 执行sql语句,通过sqlId找到语句
        int num = sqlSession.insert(sqlId, student);
        // mybatis默认是不自动提交事务的,所以insert、update、delete后要手动提交事务
        sqlSession.commit();
        return num;
    }
}

单元测试文件StudentTest.java中的内容为:

package cn.ecnu;

import cn.ecnu.dao.StudentDAO;
import cn.ecnu.dao.impl.StudentDAOImpl;
import cn.ecnu.domain.Student;
import org.junit.Test;

import java.util.List;

public class StudentTest {

    @Test
    public void selectStudentsTest() {
        StudentDAO dao = new StudentDAOImpl();
        /*
         * List<Student> studentList = dao.selectStudents(); 调用
         * 1. dao对象,雷翔是SudentDAO,全限定名称是:cn.ecnu.dao.StudentDAO
         *   全限定名称和namespace是一样的
         *
         * 2. 方法名称,selectStudents,这个方法是mapper文件中的id值
         *
         * 3. 通过dao中方法的返回值也可以确定MyBatis要调用的SqlSession的方法
         *   如果返回值是List,调用的是SqlSession.selectList()方法
         *   如果返回值非List的,如int,看mapper中的标签是<insert>、<update>就会调用
         *   SqlSession的insert、update等方法
         *
         * MyBatis的动态代理:MyBatis根据dao的方法调用,获取执行sql语句的信息。
         *   MyBatis根据dao接口,创建出一个dao接口的实现类,并创建这个类的对象,
         *   完成SqlSession调用方法,访问数据库。
         * */
        List<Student> studentList = dao.selectStudents();
        studentList.forEach(System.out::println);
    }

    @Test
    public void insertStudentTest() {
        Student student = new Student();
        student.setId(1004);
        student.setName("刘备");
        student.setEmail("liubei@sina.com");
        student.setAge(22);
        StudentDAO dao = new StudentDAOImpl();
        int num = dao.insertStudent(student);
        System.out.println("insert操作的执行结果:" + num);
    }
}

其他文件和之前的保持一致。

第三章

1. 动态代理

使用SqlSession.getMapper(dao接口.class)获取这个dao接口的对象。

项目名为03-proxy-dao,目录结构如下:

【学习笔记】MyBatis学习笔记

相比于02-MyBatisUtils,删除了实现类文件StudentDAOImpl.java,将StudentTest.java修改为:

import cn.ecnu.dao.StudentDAO;
import cn.ecnu.domain.Student;
import cn.ecnu.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class StudentTest {

    @Test
    public void selectStudentsTest() {
        /*
         * 使用mybatis的动态代理机制,使用SqlSession.getMapper(接口方法)
         * getMapper能获取dao接口对应的实现类对象
         * */
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        StudentDAO dao = sqlSession.getMapper(StudentDAO.class);
        // 调用dao的方法,执行数据库的操作
        List<Student> students = dao.selectStudents();
        for (Student stu : students) {
            System.out.println("学生: " + stu);
        }
    }

    @Test
    public void insertStudentTest() {
        Student student = new Student();
        student.setId(1005);
        student.setName("关羽");
        student.setEmail("guanyu@sina.com");
        student.setAge(22);
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        StudentDAO dao = sqlSession.getMapper(StudentDAO.class);
        int num = dao.insertStudent(student);
        System.out.println("insert操作的执行结果:" + num);
    }
}

2. 传入参数

从java代码中把数据传入到mapper文件的sql语句中。parameterType:写在mapper文件中的一个属性,表示dao接口中的

(1) 一个简单类型的参数

mybatis把java的基本数据类型和String类型都叫做简单类型。在mapper文件获取简单类型的一个参数的值,使用 #{任意字符}

项目名为04-param,目录结构为:

【学习笔记】MyBatis学习笔记

StudentDAO.java的内容修改为:

package cn.ecnu.dao;

import cn.ecnu.domain.Student;

import java.util.List;

// 接口操作student表
public interface StudentDAO {
    // 根据id查询
    /*
     * 一个简单类型的参数
     * 简单类型:mybatis把java的基本数据类型和String类型都叫做简单类型
     * 在mapper文件获取简单类型的一个参数的值,使用 #{任意字符}
     * */
    public Student selectStudentById(Integer id);
}

StudentDAO.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="cn.ecnu.dao.StudentDAO">
    <!--
        parameterType:dao接口中方法参数的数据类型
        parameterType的值是java的数据类型全限定名称或mybatis定义的别名
        例如:parameterType="java.lang.Integer"
              parameterType="int"

        注意:parameterType不是强制的,mybatis通过反射机制能够返现接口参数的类型,所以可以没有,一般不写

        使用#{}之后,mybatis执行sql的时候使用的是jdbc中的PreparedStatement对象
        由mybatis执行下面的代码:
        1. mybatis创建connection、PreparedStatement对象
            Stirng sql = "select id,name,email,age from student where id=?;"
            PreparedStatement pst = conn.preparedStatement(sql);
            pst.setInt(1.1001);

        2. 执行sql封装为resultType="cn.ecnu.domain.Student"这个对象
            ResultSet rs = ps.executeQuery();
            Student student = null;
            while(rs.next()){
                // 从数据库的表中取一行数据,存储到一个java对象属性中
                student = new Student();
                student.setId(rs.getInt("id"));
                student.setName(rs.getString("name"));
                student.setEmail(rs.getString("email"));
                student.setAge(rs.getInt("age"));
            }

            // 赋给dao方法调用的返回值
            return student;
    -->
    <select id="selectStudentById" parameterType="java.lang.Integer" resultType="cn.ecnu.domain.Student">
        select id,name,email,age from student where id=#{id};
    </select>
</mapper>

StudentTest.java中的内容修改为:

package cn.ecnu;

import cn.ecnu.dao.StudentDAO;
import cn.ecnu.domain.Student;
import cn.ecnu.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

public class StudentTest {

    @Test
    public void selectStudentByIdTest() {
        /*
         * 使用mybatis的动态代理机制,使用SqlSession.getMapper(接口方法)
         * getMapper能获取dao接口对应的实现类对象
         * */
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        StudentDAO dao = sqlSession.getMapper(StudentDAO.class);
        // 调用dao的方法,执行数据库的操作
        Student student = dao.selectStudentById(1002);
        System.out.println(student);
    }
}

(2) 多个参数 - 使用@Param

使用@Param命名参数。在StudentDAO.java中添加:

/*
* 多个参数:命名参数,在形参定义的前面加入@Param("自定义参数名称")
* */
List<Student> selectMultiPram(@Param("myname") String name, @Param("myage") Integer age);

在StudentDAO.xml中添加:

<!-- 多个参数,使用@Param命名 -->
<select id="selectMultiPram" resultType="cn.ecnu.domain.Student">
    select id,name,email,age from student where name=#{myname} or age=#{myage};
</select>

在StudentTest.java中添加:

@Test
public void selectMultiParmTest() {
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    StudentDAO dao = sqlSession.getMapper(StudentDAO.class);
    // 调用dao的方法,执行数据库的操作
    List<Student> studentList = dao.selectMultiPram("张飞", 22);
    for (Student student : studentList) {
        System.out.println(student);
    }
}

(3) 多个参数 - 使用对象

添加查询参数的实体类cn.ecnu.vo.QueryParam.java,其内容为:

package cn.ecnu.vo;

import lombok.Data;

@Data
public class QueryParam {
    private String paramName;
    private Integer paramAge;
}

在StudentDAO.java中添加:

/*
* 多个参数,使用java对象作为接口中方法的参数
* */
List<Student> selectMultiObject(QueryParam param);

在StudentDAO.xml中添加:

<!--
   多个参数,使用java对象的属性值作为参数实际值
   使用对象语法:#{属性名, javaType=类型名称,jdbcType=数据类型}
               javaType:指java中属性的数据类型
               jdbcType:指在数据库中的数据类型
               例如:#{paramName, javaType=java.lang.String, jdbcType=VARCHAR}

   简化方式:#{属性名},javaType、jdbcType的值mybatis反射能获取,不用提供
-->
<select id="selectMultiObject" resultType="cn.ecnu.domain.Student">
    select id,name,email,age from student where
    name=#{paramName, javaType=java.lang.String, jdbcType=VARCHAR}
    or age=#{paramAge, javaType=java.lang.Integer, jdbcType=INTEGER};
</select>

在StudentTest.java中添加:

@Test
public void selectMultiObjectTest() {
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    StudentDAO dao = sqlSession.getMapper(StudentDAO.class);

    QueryParam param = new QueryParam();
    param.setParamName("张三");
    param.setParamAge(21);

    // 调用dao的方法,执行数据库的操作
    List<Student> studentList = dao.selectMultiObject(param);
    for (Student student : studentList) {
        System.out.println(student);
    }
}

(4) 多个参数 - 按位置

在StudentDAO.xml中使用:

  • mybatis3.4之前:#{0}、#{1}等
  • mybatis3.4之后:#{arg0}、#{arg1}等

了解即可,容易出错,不建议用。

(5) 多个参数 - 使用Map

StudentDAO.java中的形参为一个Map

StudentDAO.xml中使用#{map的key}

了解即可。

(6) #{}和${}的区别

  • #使用?占位符在sql语句中做占位,使用PreparedStatement执行sql,效率高
  • #能够避免sql注入,更安全
  • $不使用占位符,是字符串连接方式,使用Statement对象执行sql,效率低
  • $有sql注入的风险,缺乏安全性
  • $可以替换表名或列名

3. 输出结果

(1) 结果类型resultType

mybatis执行了sql 语句,得到java对象

resultType结果类型:执行了sql语句之后,数据转为的java对象,该对象的类型是任意的,可以是实体类,也可以是int类型等。

处理方式:

  • mybatis执行sql语句,然后调用类的无参构造方法,创建对象
  • mybatis把ResultSet指定列值赋给同名的属性

resultType的值:

  • 全限定类名
  • 类型的别名,如java.lang.Integer别名是int

(2) 定义自定义类型的别名

创建05-returnResult项目,在mybatis主配置文件中定义,使用<typeAlias>定义别名。可以在resultType中使用自定义别名。

在mybatis.xml中的<configuration>标签下添加:

<!-- settings:控制mybatis全局行为,可打印日志 -->
<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!-- 定义别名 -->
<typeAliases>
    <!--
        第一种方式:
        可以指定一个类型一个自定义别名
        type:自定义类型的全限定名称
        alias:别名(短小,易记的)
    -->
    <typeAlias type="cn.ecnu.domain.Student" alias="student"/>

    <!--
        第二种方式:
        <package> name是包名,这个包中的所有类,类名就是别名(类名不区分大小写)
		当多个不同包下存在相同的实体类时,会出问题,还是全限定类名最稳妥
    -->
    <package name="cn.ecnu.domain"/>
</typeAliases>

然后在StudentDAO.xml中将resultType中的内容改为student,运行单元测试和之前结果一样。

返回类型还可以设为Map<Object,Object>,只能返回一行记录,键即表中的字段名,值为字段对应的值。

(3) resultMap

resultMap和resultType不能同时使用

结果映射, 指定列名和java对象属性的对应关系,使用场景:

  • 自定义列赋值给哪个属性
  • 当列名和属性名不一致时,必须使用resultMap

在StudentDAO.java中添加:

List<Student> selectAllStudents();

在StudentDAO.xml中的mapper标签下添加:

<!--
    使用resultMap:
    1. 在<mapper>标签下自定义resultMap
    2. 在select标签使用resultMap来引用所定义的
-->

<!--
    自定义resultMap
    id:自定义名称,表示你定义的这个resultMap
    type:java类型的全限定名称
-->
<resultMap id="studentMap" type="cn.ecnu.domain.Student">
    <!-- 列名和java属性的对应关系 -->
    <!-- 主键列使用id标签
         column:表的字段名
         property:java类型的属性名
     -->
    <id column="id" property="id"></id>
    <!-- 非主键列使用result -->
    <result column="name" property="name"></result>
    <result column="email" property="email"></result>
    <result column="age" property="age"></result>
</resultMap>
<select id="selectAllStudents" resultMap="studentMap">
    select id,name,email,age from student;
</select>

在StudentTest.java中添加:

@Test
public void selectAllStudentsTest() {
    /*
     * 使用mybatis的动态代理机制,使用SqlSession.getMapper(接口方法)
     * getMapper能获取dao接口对应的实现类对象
     * */
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    StudentDAO dao = sqlSession.getMapper(StudentDAO.class);
    // 调用dao的方法,执行数据库的操作
    List<Student> studentList = dao.selectAllStudents();
    for (Student student : studentList) {
        System.out.println(student);
    }
}

(4) 处理字段名和属性名不同的第二种方法

创建MyStudent.java实体类:

package cn.ecnu.domain;

import lombok.Data;

// 推荐和表名一样,方便记忆
@Data
public class MyStudent {
    // 定义属性,目前要求属性名和列名一致
    private Integer stuId;
    private String stuName;
    private String stuEmail;
    private Integer stuAge;
}

在StudentDAO.java中添加:

 List<MyStudent> selectMyStudent();

在StudentDAO.xml中添加:

<!-- 字段名与属性名不同时的第二种处理方法 -->
<select id="selectMyStudent" resultType="cn.ecnu.domain.MyStudent">
    select id as stuId, name as stuName,email as stuEmail, age as stuAge from student;
</select>

在StudentTest.java中添加:

@Test
public void selectMyStudentTest() {
    /*
     * 使用mybatis的动态代理机制,使用SqlSession.getMapper(接口方法)
     * getMapper能获取dao接口对应的实现类对象
     * */
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    StudentDAO dao = sqlSession.getMapper(StudentDAO.class);
    // 调用dao的方法,执行数据库的操作
    List<MyStudent> studentList = dao.selectMyStudent();
    for (MyStudent student : studentList) {
        System.out.println(student);
    }
}

(5) 模糊查询的两种方式

在StudentDAO.java中添加:

/*
* 第一种模糊查询,在java代码中指定like的内容
* */
List<Student> selectLikeOne(String name);

/*
* 第二种模糊查询,在mapper中拼接上%
* */
List<Student> selectLikeTwo(String name);

在StudentDAO.xml中添加:

<!-- 第一种模糊查询,在java代码中指定like的内容 -->
<select id="selectLikeOne" resultType="cn.ecnu.domain.Student">
    select id,name,email,age from student where name like #{name};
</select>

<!-- 第二种模糊查询,在mapper文件中拼接like的内容,"%"与#{name}之间的空格必须有 -->
<select id="selectLikeTwo" resultType="cn.ecnu.domain.Student">
    select id,name,email,age from student where name like "%" #{name} "%";
</select>

在StudentTest.java中添加:

@Test
public void selectLikeOneTest() {
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    StudentDAO dao = sqlSession.getMapper(StudentDAO.class);
    // 准备好like的内容
    String name = "%李%";
    List<Student> studentList = dao.selectLikeOne(name);
    for (Student student : studentList) {
        System.out.println(student);
    }
}

@Test
public void selectLikeTwoTest() {
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    StudentDAO dao = sqlSession.getMapper(StudentDAO.class);
    // 准备好like的内容
    String name = "张";
    List<Student> studentList = dao.selectLikeTwo(name);
    for (Student student : studentList) {
        System.out.println(student);
    }
}

第四章 动态SQL

动态sql指的是sql的内容是变化的,可以根据条件获取到不同的sql语句,主要是where部分发生变化。

动态sql的实现,使用的是mybatis提供的标签:<if><where><foreach>

1. <if>标签

语法:

<if test="判断java对象的属性值">
    部分sql语句
</if>

在StudentDAO.java中添加:

// 动态sql,使用java对象作为参数
List<Student> selectStudentIf(Student student);

在StudentDAO.xml中添加:

<!-- if
    <if: test="使用java对象的属性值作为判断条件">

    注意where后面的1 = 1的作用是当name为空时后面跟or会出问题
-->
<select id="selectStudentIf" resultType="cn.ecnu.domain.Student">
    select id,name,email,age from student
    where 1 = 1
    <if test="name != null and name != ''">
        and name = #{name}
    </if>
    <if test="age > 0">
        or age > #{age}
    </if>
</select>

在StudentTest.java中添加:

@Test
public void selectStudentIfTest() {
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    StudentDAO dao = sqlSession.getMapper(StudentDAO.class);

    Student student = new Student();
    student.setName("李四");
    student.setAge(20);

    // 调用dao的方法,执行数据库的操作
    List<Student> studentList = dao.selectStudentIf(student);
    for (Student stu : studentList) {
        System.out.println(stu);
    }
}

2. <where>标签

用来包含多个<if>标签的,当多个if有一个成立时,<where>会自动增加一个where关键字,并去掉if中多余的and、or等。

在StudentDAO.java中添加:

// where的使用
List<Student> selectStudentWhere(Student student);

在StudentDAO.xml中添加:

<!--
    <where>里面包含多个<if>,多个if有一个成立时,<where>会自动增加一个where关键字,
    并去掉if中多余的and、or等。
-->
<select id="selectStudentWhere" resultType="cn.ecnu.domain.Student">
    select id,name,email,age from student
    <where>
        <if test="name != null and name != ''">
            name = #{name}
        </if>
        <if test="age > 0">
            or age > #{age}
        </if>
    </where>
</select>

在StudentTest.java中添加:

@Test
public void selectStudentWhereTest() {
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    StudentDAO dao = sqlSession.getMapper(StudentDAO.class);

    Student student = new Student();
    student.setAge(20);

    // 调用dao的方法,执行数据库的操作
    List<Student> studentList = dao.selectStudentWhere(student);
    for (Student stu : studentList) {
        System.out.println(stu);
    }
}

3. <foreach>标签

循环java中的数组,list集合的。主要用在sql的in语句中。比如查询学生id为1001、1002、1003的三个学生。

<foreach collection="" item="" open="" close="" separator="">   
</foreach>
  • collection:接口中方法参数的类型,如果是数组则使用array,如果是List则使用list

  • item:自定义的,表示数组和集合成员的变量

  • open:循环开始时的字符

  • close:循环结束时的字符

  • separator:集合成员之间的分隔符

在StudentDAO.java中添加:

List<Student> selectStudentForeachOne(List<Integer> idList);

在StudentDAO.xml中添加:

<select id="selectStudentForeachOne" resultType="cn.ecnu.domain.Student">
    select id,name,email,age from student
    where id in
    <foreach collection="list" item="myid" open="(" close=")" separator=",">
        #{myid}
    </foreach>
</select>

在StudentTest.java中添加:

@Test
public void selectStudentForeachOneTest() {
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    StudentDAO dao = sqlSession.getMapper(StudentDAO.class);

    List<Integer> idList = new ArrayList<>();
    idList.add(1001);
    idList.add(1002);
    idList.add(1003);

    // 调用dao的方法,执行数据库的操作
    List<Student> studentList = dao.selectStudentForeachOne(idList);
    for (Student stu : studentList) {
        System.out.println(stu);
    }
}

在StudentDAO.java中添加:

List<Student> selectStudentForeachTwo(List<Student> Students);

在StudentDAO.xml中添加:

<select id="selectStudentForeachTwo" resultType="cn.ecnu.domain.Student">
    select id,name,email,age from student
    where id in
    <foreach collection="list" item="student" open="(" close=")" separator=",">
        #{student.id}
    </foreach>
</select>

在StudentTest.java中添加:

@Test
public void selectStudentForeachTwoTest() {
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    StudentDAO dao = sqlSession.getMapper(StudentDAO.class);

    List<Student> students = new ArrayList<>();
    Student s1 = new Student();
    s1.setId(1001);
    students.add(s1);
    Student s2 = new Student();
    s2.setId(1002);
    students.add(s2);
    Student s3= new Student();
    s3.setId(1003);
    students.add(s3);

    // 调用dao的方法,执行数据库的操作
    List<Student> studentList = dao.selectStudentForeachTwo(students);
    for (Student stu : studentList) {
        System.out.println(stu);
    }
}

4. 代码片段

复用一些sql语句

先定义

<sql id="自定义名称(唯一)"> 
    sql语句、表名、字段名等
</sql>

再使用

<include refid="id的值" />

在StudentDAO.xml中添加:

<sql id="studentSql">
    select id,name,email,age from student
</sql>
<select id="selectStudentForeachTwo" resultType="cn.ecnu.domain.Student">
    <include refid="studentSql"/>
    where id in
    <foreach collection="list" item="student" open="(" close=")" separator=",">
        #{student.id}
    </foreach>
</select>

第五章

1. 数据库的属性配置文件

把数据库连接信息放到一个单独的文件中,和mybatis主配置文件分开,目的是便于修改、保存、处理多个数据库的信息。

  • 在resources目录定义一个属性配置文件,xxx.properties,例如jdbc.properties。在属性配置文件中,定义数据,格式是 key = value。key一般使用.做多级目录,例如 jdbc.mysql.driver

    # jdbc.properties文件中的内容
    jdbc.driver = com.mysql.jdbc.Driver
    jdbc.url = jdbc:mysql://localhost:3306/mybatis
    jdbc.username = root
    jdbc.password = root
    
  • 在mybatis的主配置文件中,使用<property>标签指定文件的位置,在需要使用值的地方,使用${}

    <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>
    

2. 多mapper文件的处理

当有多个mapper文件时,在mybatis的主配置文件中有两种导入方式

方式一:

<mappers>
    <!--一个mapper标签指定一个文件的位置,从类路径开始的路径信息-->
    <mapper resource="cn/ecnu/dao/StudentDAO.xml"/>
</mappers>

方式二:

<mappers>
    <!-- 第二种方式:使用包名
      name:xml文件(mapper文件)所在的包名,这个包中的所有xml文件一次性加载
         使用package的要求:
         1. mapper文件名称需要和接口名称一样,区分大小写
         2. mapper文件和dao接口需要在同一目录下
    -->
    <package name="cn.ecnu.dao"/>
</mappers>

注意这两种方式不能并存,否则会报错。

第六章

PageHelper:做数据分页的

在pom.xml文件中添加依赖:

<!-- 加入PageHelper的依赖,做分页 -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.10</version>
</dependency>

在mybatis主配置文件mybatis.xml中添加:

<!-- 在environments标签之前加入 -->
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"/>
</plugins>

在StudentDAO.java中添加接口方法:

// 使用PageHelper分页插件
List<Student> selectAll();

在StudentDAO.xml中添加对应的SQL,注意此处不能以分号结尾,否则字符串拼接时会出错:

<select id="selectAll" resultType="cn.ecnu.domain.Student">
    select * from student order by id
</select>

在StudentTest.java中添加单元测试文件:

@Test
public void selectAllTest() {
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    StudentDAO dao = sqlSession.getMapper(StudentDAO.class);
    /*
     * 加入PageHelper的方法,分页
     * pageNum:第几页,从1开始
     * pageSize:一页中有多少行数据
     * */
    PageHelper.startPage(1, 2);
    List<Student> studentList = dao.selectAll();
    for (Student stu : studentList) {
        System.out.println(stu);
    }
}
上一篇:MyBatis


下一篇:python 实验环境