1

前言

Mybatis是一款Java持久层框架,内部将操作数据库需要的Jdbc相关代码进行了封装,同时能将SQL语句执行结果与Pojo直接进行映射。本篇文章将先对Jdbc进行学习,并在此基础上学习Mybatis的基础使用,无论是Jdbc还是Mybatis,均是基于原生组件,不会与Spring等框架进行整合。

Mybatis版本:3.5.6

正文

一. Jdbc基础

Jdbc全称为JavaDataBase Connectivity,即Java数据库连接,基于Jdbc可以获取数据库连接并执行SQL语句,还能处理SQL语句的执行结果集。下面将对Jdbc的操作步骤进行介绍。

1. 加载数据库驱动

首先需要加载数据库驱动,这里以Mysql数据库驱动为例,如下所示。

Class.forName("com.mysql.jdbc.Driver");

2. 获取数据库连接

然后需要获取数据库连接,如下所示。

// url用于标识数据库位置,即告诉Jdbc连接哪个数据库
String url = "jdbc:mysql://127.0.0.1:3306/test";
// 数据库用户名和密码
String username = "root";
String password = "root";
// Connection是数据库编程中的一个重要对象,客户端与数据库的所有交互均依赖该对象
Connection connection = DriverManager.getConnection(url, username, password);

Connection对象部分重要方法如下所示。

方法描述
Statement createStatement()创建向数据库发送SQLStatement对象
PreparedStatement prepareStatement(String sql)创建向数据库发送预编译SQLPreparedStatement对象
commit()提交事务
rollback()回滚事务

3. 获取Statement对象

Statement对象用于向数据库发送SQL语句,获取方式如下所示。

Statement statement = connection.createStatement();

Statement对象的常用方法如下所示。

方法描述
ResultSet executeQuery(String sql)向数据库发送查询SQL语句
int executeUpdate(String sql)向数据库发送插入,更新或删除SQL语句
boolean execute(String sql)向数据库发送任意SQL语句

使用Statement的查询示例如下所示。

Statement statement = connection.createStatement();
String sql = "SELECT * FROM worker";
ResultSet resultSet = statement.executeQuery(sql);

由于Statement的使用会引入安全效率问题,所以通常Jdbc执行SQL语句是基于PreparedStatement对象。PreparedStatement继承于Statement,使用PreparedStatement的查询示例如下所示。

String sql = "SELECT * FROM worker WHERE sex = ?"
// 获取PreparedStatement对象时就需要传入SQL语句进行预编译
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 设置参数,将第一个占位符替换为"male"
preparedStatement.setString(1, "male");
// 执行查询,查询结果由ResultSet对象封装
ResultSet resultSet = preparedStatement.executeQuery();

相较于Statement对象,PreparedStatement对象执行效率更高(因为对SQL语句完成了预编译),并且可以防止SQL注入(因为获取PreparedStatement对象时就需要传入SQL语句进行预编译,此时不会产生用户输入数据改变SQL语句结构的现象)。

4. 处理执行结果

ResultSet代表JdbcSQL语句的执行结果。ResultSet封装执行结果时,采用的是表格的方式,ResultSet对象维护了一个指向表格数据行的游标,初始状态时,游标在第0行(此时没有指向结果数据),调用next()方法后,游标会指向结果数据里的第一行数据,此时可以通过ResultSet对象来操作游标指向的这一行数据。ResultSet的常用方法如下所示。

方法描述
boolean next()游标移动到下一行
boolean previous()游标移动到上一行
boolean absolute( int row )游标移动到指定行
Object getObject(int columnIndex)获取游标指向的行的第columnIndex列的任意类型数据
Object getObject(String columnLabel)获取游标指向的行的columnLabel列的任意类型数据

5. 释放连接

操作完数据库后,需要释放连接,如下所示。

if (resultSet != null) {
    resultSet.close();
}
if (preparedStatement != null) {
    preparedStatement.close();
}
if (connection != null) {
    connection.close();
}

二. Mybatis基础使用

1. Mybatis结构

Mybatis的一个整体结构如下所示。

上图中出现的Mybatis的组件的说明如下。

组件说明
mybatis-config.xmlMybatis的全局配置文件。配置Mybatis的运行环境(数据库连接等)。
Mapper.xml映射文件。映射文件中配置了操作数据库的SQL语句。
SqlSessionFactory通过mybatis-config.xml文件构建SqlSessionFactory,称为会话工厂。用于创建SqlSession
SqlSession通过SqlSessionFactory创建SqlSession,称为会话。操作数据库需要通过SqlSession进行。
ExecutorSqlSession内部通过Executor执行数据库操作。
MappedStatementExecutor通过MappedStatement在执行SQL语句前将输入的参数映射到SQL语句中,在执行SQL语句后将输出结果映射到设置的输出结果类型中。

2. 原始Mybatis使用

原始Mybatis的使用中,在引入Mybatis的包以及配置好mybatis-config.xml之后,最重要的就是编写映射文件(Mapper.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="MyMapper">
    <select id="selectWorker" parameterType="java.lang.String" resultType="com.test.po.Worker">
        SELECT * FROM worker WHERE sex = #{sex}
    </select>
</mapper>

原始Mybatis执行映射文件时,首先需要获取SqlSession,然后通过SqlSession来调用映射文件。获取SqlSession的步骤如下所示。

// 指定全局配置文件
String resource = "mybatis-config.xml";
// 读取全局配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 构建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();

SqlSession调用映射文件的示例如下所示。

// 第一个参数为:(映射文件的)命名空间 + "." + (映射文件的SQL语句的)id
// 第二个参数为:需要执行的SQL语句的参数值
List<Worker> workers = sqlSession.selectList("MyMapper.selectWorker", "male");

最后不要忘记关闭SqlSession,如下所示。

if (sqlSession != null) {
    sqlSession.close();
}

3. 动态代理Mybatis使用

动态代理Mybatis使用时,需要编写映射文件和映射接口,这里先给出映射文件,如下所示。

<mapper namespace="com.test.mapper.WorkerMapper">
    <select id="selectWorkerBySex" parameterType="java.lang.String" resultType="com.test.po.Worker">
        SELECT * FROM worker WHERE sex = #{sex}
    </select>
</mapper>

上面的映射文件中,<mapper>标签的namespace属性需要设置为与映射文件关联的映射接口的全限定名,<select>标签的id属性需要与映射接口定义的方法名一致,即映射接口定义的每一个方法需要和映射文件中的某一条SQL相对应,Mybatis会为映射接口生成映射实例,通过调用映射实例的方法来完成对数据库的操作。上面的映射文件关联的映射接口如下所示。

public interface WorkerMapper {

    List<Worker> selectWorkerBySex(String sex);

}

动态代理Mybatis执行时,首先还是需要获取SqlSession,然后通过SqlSession来获取映射接口的实例。获取SqlSession的步骤如下所示。

// 指定全局配置文件
String resource = "mybatis-config.xml";
// 读取全局配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 构建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();

获取映射接口的实例的步骤如下所示。

WorkerMapper workerMapper = sqlSession.getMapper(WorkerMapper.class);

获取映射接口的实例后,通过调用映射接口的实例的方法来执行SQL语句,如下所示。

List<Worker> list = workerMapper.selectWorkerBySex("male");

最后关闭SqlSession,如下所示。

if (sqlSession != null) {
    sqlSession.close();
}

三. Mybatis标签语法

本节对映射文件中的常用标签进行分析,无特殊说明时,默认Mybatis的使用是基于动态代理的方式。

1. <select>标签

<select>标签表示查询SQL语句,标签常用属性及说明如下表所示。

属性说明
id当前namespace下的唯一标识,要求id的值与映射接口中的方法名称一致。
parameterType传入SQL语句的参数的类型的全限定名(可以省略,Mybatis可以自己推断出传入参数的类型)。
resultTypeSQL语句执行后返回的数据的类型的全限定名,如果返回的是集合类型,则resultType为集合存储的元素的类型的全限定名。
resultMap<resultMap>标签配合使用。

如果需要传入多个参数,可以通过传入Map或者Java Bean来实现。下面将举两个例子来说明<select>标签的使用。

例子1:通过Map传入两个参数,分别为age和salary,然后查询年龄大于age并且工资大于salary的所有工人数据。

映射文件如下所示。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.test.mapper.WorkerMapper">
    <select id="selectAgeSalaryByMap" parameterType="java.util.Map" 
            resultType="com.test.po.Worker">
        SELECT * FROM worker WHERE age > #{age} and salary > #{salary}
    </select>
</mapper>

映射接口如下所示。

public interface WorkerMapper {

    List<Worker> selectAgeSalaryByMap(Map<String, Long> map);

}

测试代码如下所示(省略SqlSession的获取)。

......
WorkerMapper workerMapper = sqlSession.getMapper(WorkerMapper.class);
Map<String, Long> map = new HashMap<>();
map.put("age", 20L);
map.put("salary", 10000L);
List<Worker> workers = workerMapper.selectAgeSalaryByMap(map);
......

例子2:通过Java Bean传入两个参数,分别为age和salary,然后查询年龄大于age并且工资大于salary的所有工人数据。

Java Bean如下所示。

public class SelectInfo {

    private Long age;
    private Long salary;

    public Long getAge() {
        return age;
    }

    public void setAge(Long age) {
        this.age = age;
    }

    public Long getSalary() {
        return salary;
    }

    public void setSalary(Long salary) {
        this.salary = salary;
    }
    
}

映射文件如下所示。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.test.mapper.WorkerMapper">
    <select id="selectAgeSalaryByBean" parameterType="com.test.po.SelectInfo" 
            resultType="com.test.po.Worker">
        SELECT * FROM worker WHERE age > #{age} and salary > #{salary}
    </select>
</mapper>

映射接口如下所示。

public interface WorkerMapper {

    List<Worker> selectAgeSalaryByBean(SelectInfo selectInfo);

}

测试代码如下所示(省略SqlSession的获取)。

......
WorkerMapper workerMapper = sqlSession.getMapper(WorkerMapper.class);
SelectInfo selectInfo = new SelectInfo();
selectInfo.setAge(20L);
selectInfo.setSalary(10000L);
List<Worker> workers = workerMapper.selectAgeSalaryByBean(selectInfo);
......

2. <insert>标签

<insert>标签表示插入SQL语句,Mybatis执行完一条插入语句后,将返回一个整数表示其影响的行数。<insert>标签属性与<select>标签属性大部分相同,下表给出不同的部分。

属性说明
keyProperty设置为主键对应的属性时,可以获取插入操作后插入数据的主键值。
useGeneratedKeys该属性为true时,Mybatis会使用JDBCgetGeneratedKeys()方法获取数据库内部产生的主键。

如下给出一个插入数据后获取插入数据的自增主键的示例。映射文件如下所示。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http:// mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.test.mapper.WorkerMapper">
    <insert id="insertWorker" parameterType="com.test.po.Worker"
            keyProperty="id" useGeneratedKeys="true">
        INSERT INTO
        worker (name, sex, age, salary)
        VALUES
        (#{name}, #{sex}, #{age}, #{salary})
    </insert>
</mapper>

映射接口如下所示。

public interface WorkerMapper {

    Integer insertWorker(Worker worker);

}

Worker类结构如下所示。

public class Worker {

    private long id;
    private String name;
    private String sex;
    private long age;
    private long salary;

    public Worker() {}

    public Worker(String name, String sex,
                  long age, long salary) {
        this.name = name;
        this.sex = sex;
        this.age = age;
        this.salary = salary;
    }

    // 省略getter和setter
    ......

}

worker表的创建语句如下。

CREATE TABLE worker(
    id INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT,
    name VARCHAR(30) NOT NULL,
    sex VARCHAR(30) NOT NULL,
    age INT(11) NOT NULL,
    salary INT(11) NOT NULL
)

测试代码如下所示(省略SqlSession的获取)。

...
WorkerMapper workerMapper = sqlSession.getMapper(WorkerMapper.class);
Worker worker = new Worker("Lee", "male", 20L, 10000L);
Integer result = workerMapper.insertWorker(worker);
System.out.println(result + " pieces of data were inserted.");
System.out.println("The primary key of the inserted data is " + worker.getId());
...

3. <update>和<delete>标签

这两个标签的属性与<select><insert>标签的属性大体一致,并且在执行后会返回一个整数表示影响的记录数。

4. <resultMap>标签

从数据库中查询出数据时,通常会将查询出来的数据映射成一个Java Bean,但是如果数据库表的列名与Java Bean的属性名不一致时,会无法进行映射,此时需要使用<resultMap>标签来指定表列名和Java Bean属性名之间的映射关系。现在创建一张名为book的表,如下所示。

CREATE TABLE book(
    id INT(11) PRIMARY KEY AUTO_INCREMENT,
    b_name VARCHAR(255) NOT NULL,
    b_price INT(11) NOT NULL
)

创建一个Book类,用于与book表的查询结果进行映射,如下所示。

public class Book {

    private long id;
    private String bookName;
    private long bookPrice;

    // 省略getter和setter
    ......

}

可以看到,Book类的bookNamebookPrice属性与book表的b_nameb_price列的名称不一致,从book表查询出的数据无法映射到Book类上,此时需要使用<resultMap>标签来解决无法映射的问题。映射文件如下所示。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.test.mapper.BookMapper">
    <resultMap id="bookResultMap" type="com.test.po.Book">
        <result column="id" property="id"/>
        <result column="b_name" property="bookName"/>
        <result column="b_price" property="bookPrice"/>
    </resultMap>

    <select id="selectBookByName" parameterType="java.lang.String"
            resultMap="bookResultMap">
        SELECT * FROM book WHERE b_name=#{bookName}
    </select>
</mapper>

<resultMap>标签的id属性表示当前namespace下的唯一标识,可以通过<resultMap>标签的id属性来引用该<resultMap><resultMap>标签的type属性表映射类的全限定名,比如需要与Book类进行映射,则type字段应该为Book类的全限定名。<resultMap>标签下,一个<result>标签代表一对表列名和类属性名的映射,<result>标签的column属性为表的列名,<result>标签的property属性为类属性名。

上面映射文件对应的映射接口如下所示。

public interface BookMapper {

    List<Book> selectBookByName(String bookName);

}

测试代码如下所示(省略SqlSession的获取)。

......
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
List<Book> mathBooks = bookMapper.selectBookByName("Math");
......

如果Book类中有一个属性为一个类,如下所示。

Book

public class Book {

    private long id;
    private String bookName;
    private long bookPrice;

    private BookStore bookStore;

    // 省略getter和setter
    ......

}

BookStore

public class BookStore {

    private long bsId;
    private String bsName;

    // 省略getter和setter
    ......

}

此时映射文件如下所示。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.test.mapper.BookMapper">
    <resultMap id="bookResultMap" type="com.test.po.Book">
        <result column="id" property="id"/>
        <result column="b_name" property="bookName"/>
        <result column="b_price" property="bookPrice"/>

        <association property="bookStore" javaType="com.test.po.BookStore">
            <result column="bs_id" property="bsId"/>
            <result column="bs_name" property="bsName"/>
        </association>
    </resultMap>

    <select id="selectBookByName" parameterType="java.lang.String"
            resultMap="bookResultMap">
        SELECT * FROM book WHERE b_name=#{bookName}
    </select>
</mapper>

关于Mybatis标签的基础语法就分析到这里,其中<resultMap>标签作为Mybatis中的功能最强大标签,其所能实现的功能远不止上述介绍的功能,后续会单独就<resultMap>标签的使用进行深入学习。

总结

本文对JDBC的基础使用和Mybatis的基础使用进行了分析,并针对Mybatis的映射文件中的常用标签的基础语法进行了介绍。通常Mybatis的使用需要编写映射文件和映射接口,且一个映射文件与一个映射接口相对应,Mybatis会使用JDK动态代理为映射接口生成代理对象,当调用映射接口的方法时,调用请求会被代理对象拦截,然后根据映射接口全限定名+方法名定位到相应的MappedStatement然后执行SQL语句。


半夏之沫
65 声望31 粉丝