coding……
但行好事 莫问前程

Mybatis源码解读『Java操作Mysql』

在工程开发中,大概率会使用到关系型数据库,一般在最常用的就是Mysql了。当在Java工程中使用Mysql时,我们需要获取与Mysql的连接,然后才能使用一些方法,操作Mysql。在使用Java操作Mysql的问题上,我们最开始学习时,一般是通过JDBC实现的。然后我们一般会接触到一些ORM框架,比如Mybatis、Spring Data Jpa(Hibernate)。在国内,Mybatis应用更多,生态也更加完善。Mybatis虽然使用起来比较简单,但是还是有很多细节值得探讨,这对我们更好地了解ORM框架有很大的帮助。所以本篇文章开始,会探索ORM框架Mybatis的相关实现细节,这也是我们学习最常用开发框架SSM的最后一个部分。

本篇文章,我们先来看一些基础问题,比如Java怎么操作Mysql,为什么要使用ORM框架,Mybatis能给我们带来什么好处等。

1. JDBC

提到Java操作Mysql,不可避免的要提到JDBC。JDBC(JavaDataBase Connectivity)就是Java数据库连接,说白了就是用Java语言来操作数据库。

早期SUN公司编写一套可以连接所有数据库的API,但是发现这是一件很困难的事情,因为数据库种类繁多,并且各个厂商的数据库服务器差异也很大。后来SUN开始与数据库厂商们讨论,由SUN提供一套访问数据库的规范(一组接口),并提供连接数据库的协议标准,然后各个数据库厂商会遵循SUN的规范提供一套访问自己公司的数据库服务器的API。SUN提供的规范命名为JDBC,而各个厂商提供的遵循了JDBC规范,可以访问自己数据库的API被称之为驱动。

下面以Java连接Mysql为例,来看一下如何使用JDBC来连接和操作数据库的。

1.1 新建user_info表

# 新建数据库zhuoli_db
create database zhuoli_db;

# 新建user_info表
CREATE TABLE user_info (
    id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',
    name varchar(32) NOT NULL COMMENT '名称',
    age int(3) NOT NULL COMMENT '年龄',
    PRIMARY KEY (id)
);

1.2 创建实体类

为表user_info创建对应的实体类UserInfo。

public class UserInfo {
    private Long id;

    private String name;

    private Integer age;

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public Integer getAge() {
        return age;
    }

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

    public void setName(String name) {
        this.name = name;
    }

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

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

1.3 创建JDBC数据库连接

public class DbUtil {
    private static final String URL = "jdbc:mysql://localhost:3306/zhuoli_db";
    private static final String USER = "zhuoli";
    private static final String PASSWORD = "zhuoli";

    private static Connection conn = null;

    static{
        try {
            //1.加载驱动程序
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2. 获得数据库连接
            conn = DriverManager.getConnection(URL, USER, PASSWORD);
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection(){
        return conn;
    }
}

1.4 通过JDBC操作数据库

public class TestDao {
    //增加
    public void addUser(UserInfo userInfo) throws SQLException {
        //获取连接
        Connection conn = DbUtil.getConnection();
        //sql
        String sql = "INSERT INTO user_info(name, age)" + "values(" + "?, ?)";
        //预编译sql
        PreparedStatement ptmt = conn.prepareStatement(sql); //预编译SQL,减少sql执行

        //传参
        ptmt.setString(1, userInfo.getName());
        ptmt.setInt(2, userInfo.getAge());

        //执行
        ptmt.execute();
    }

    public void updateUserAge(Long id, Integer age) throws SQLException {
        //获取连接
        Connection conn = DbUtil.getConnection();
        //sql, 每行加空格
        String sql = "UPDATE user_info set age = ? " + " where id = ?";
        //预编译sql
        PreparedStatement ptmt = conn.prepareStatement(sql);

        //传参
        ptmt.setInt(1, age);
        ptmt.setLong(2, id);

        //执行
        ptmt.execute();
    }

    public void delUser(Long id) throws SQLException {
        //获取连接
        Connection conn = DbUtil.getConnection();
        //sql
        String sql = "delete from user_info where id= ?";

        //预编译SQL
        PreparedStatement ptmt = conn.prepareStatement(sql);

        //传参
        ptmt.setLong(1, id);

        //执行
        ptmt.execute();
    }

    public List<UserInfo> queryAllUser() throws SQLException {
        Connection conn = DbUtil.getConnection();
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery("SELECT * FROM user_info");

        List<UserInfo> userInfoList = new LinkedList<>();
        while (rs.next()) {
            UserInfo userInfo = new UserInfo();
            userInfo.setId(rs.getLong("id"));
            userInfo.setName(rs.getString("name"));
            userInfo.setAge(rs.getInt("age"));

            userInfoList.add(userInfo);
        }
        return userInfoList;
    }

    public UserInfo queryUserInfoById(Long id) throws SQLException {
        UserInfo userInfo = null;
        //获取连接
        Connection conn = DbUtil.getConnection();
        //sql
        String sql = "select * from  user_info where id = ?";
        //预编译SQL
        PreparedStatement ptmt = conn.prepareStatement(sql);
        //传参
        ptmt.setLong(1, id);
        //执行
        ResultSet rs = ptmt.executeQuery();
        while (rs.next()) {
            userInfo = new UserInfo();
            userInfo.setId(rs.getLong("id"));
            userInfo.setName(rs.getString("name"));
            userInfo.setAge(rs.getInt("age"));
        }
        return userInfo;
    }
}

综合1.2和1.3节,我们可以知道,JDBC操作数据库的步骤大致如下:

  1. 加载驱动程序
  2. 获得数据库连接
  3. 创建Statement
  4. 执行Statement

1.5 测试代码

public class JdbcTest {

    public static void main(String[] args) throws Exception {
        TestDao testDao = new TestDao();
        System.out.println("query all");
        testDao.queryAllUser().forEach(System.out::println);
        System.out.println("------------------------------");

        UserInfo addUser = new UserInfo();
        addUser.setName("Michael");
        addUser.setAge(18);
        testDao.addUser(addUser);
        System.out.println("add new User name = Michael, age = 20, after execute add: ");
        testDao.queryAllUser().forEach(System.out::println);
        System.out.println("------------------------------");

        testDao.updateUserAge(1L, 20);
        System.out.println("update userId1's age to 20, after execute update: ");
        testDao.queryAllUser().forEach(System.out::println);
        System.out.println("------------------------------");

        System.out.println("queryById, id = 1, result is:");
        System.out.println(testDao.queryUserInfoById(1L));
        System.out.println("------------------------------");
    }
}

也就是说,我们成功通过JDBC连接并对数据库进行了增删改查。

但是,上述代码中,不难发现,其实有一些不方便的地方。

  • 比如sql传参和对象映射很不方便,我们需要在代码中需要多次手动去写这部分“重复”的逻辑,
  • 每次执行sql前都要去获取连接,创建Statement

有没有什么方法,能让我们访问数据库更加方便简洁?答案就是我们接下来要介绍的ORM框架,这里我们使用Mybatis。

2. Mybatis

mybatis是一个持久层ORM框架。它内部封装了jdbc,可以通过xml或注解完成ORM映射关系配置。使开发更简洁,更高效。使用Mybatis,可以让开发者只需要关注sql语句本身,简化JDBC操作,不需要在关注加载驱动、创建连接、处理SQL语句等繁杂的过程。下面来看一下通过Mybatis又是如何连接并操作数据库的。

2.1 创建实体类

数据库和表,我们还是用上面介绍JDBC时,使用的zhuoli_db.user_info。所以第一步,我们还是创建一个实体类Model对象:

public class UserInfo {
    private Long id;

    private String name;

    private Integer age;

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public Integer getAge() {
        return age;
    }

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

    public void setName(String name) {
        this.name = name;
    }

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

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

Model实体对象,跟上述JDBC时创建的实体对象完全一致。

2.2 创建Mapper接口

public interface UserInfoMapper {
    void addUser(UserInfo userInfo);

    void updateUserAge(@Param("id") Long id, @Param("age") Integer age);

    void delUserById(Long id);

    List<UserInfo> queryAllUser();

    UserInfo queryUserInfoById(Long id);
}

2.3 定义mapper接口对应的mapper文件user_info.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhuoli.service.mybatis.explore.mapper.UserInfoMapper">

    <resultMap id="userInfoResult" type="com.zhuoli.service.mybatis.explore.model.UserInfo">
        <result property="id" column="id" jdbcType="BIGINT"/>
        <result property="name" column="name" jdbcType="VARCHAR"/>
        <result property="age" column="age" jdbcType="INTEGER"/>
    </resultMap>

    <select id="queryAllUser" resultMap="userInfoResult">
        select * from user_info;
    </select>

    <select id="queryUserInfoById" resultMap="userInfoResult" parameterType="java.lang.Long">
        select * from user_info where id = #{id};
    </select>

    <insert id="addUser" parameterType="com.zhuoli.service.mybatis.explore.model.UserInfo" useGeneratedKeys="true" keyProperty="id">
        insert into user_info (name, age) values (#{name}, #{age})
    </insert>

    <delete id="delUserById" parameterType="java.lang.Long">
        delete from user_info where id = #{id};
    </delete>

    <update id="updateUserAge">
        update user_info set age = #{age} where id = #{id}
    </update>
</mapper>

其实就是上述mapper接口的sql实现,至于为什么没有Java实现,就可以通过Mapper接口操作Mysql,后面我们再介绍。

2.4 创建Mybatis配置文件mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!-- 引入外部配置文件 -->
    <properties resource="dataSource.properties"></properties>

    <environments default="default">
        <environment id="default">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${dataSource.driver}"/>
                <property name="url" value="${dataSource.url}"/>
                <property name="username" value="${dataSource.username}"/>
                <property name="password" value="${dataSource.password}"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mapper/user_info.xml"/>
    </mappers>
</configuration>

这里数据源配置使用了占位符,跟我们之前Spring的占位符有点像,但这里其实跟Spring没有关系,其实是Mybatis通过引入外部配置文件中的属性实现的,即dataSource.properties文件中的属性,如下:

dataSource.driver=com.mysql.cj.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/zhuoli_db
dataSource.username=zhuoli
dataSource.password=zhuoli

2.5 测试代码

public class Test1 {
    public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            UserInfoMapper userInfoMapper = sqlSession.getMapper(UserInfoMapper.class);
            System.out.println("query all user");
            List<UserInfo> userInfoList = userInfoMapper.queryAllUser();
            userInfoList.forEach(System.out::println);
            System.out.println("---------------------");

            System.out.println("queryUserInfoById id = 1");
            System.out.println(userInfoMapper.queryUserInfoById(1L));
            System.out.println("---------------------");

            System.out.println("addUser");
            UserInfo addUser = new UserInfo();
            addUser.setAge(14);
            addUser.setName("paul");
            userInfoMapper.addUser(addUser);
            userInfoMapper.queryAllUser().forEach(System.out::println);
            System.out.println("---------------------");

            System.out.println("update User 1L age to 22");
            userInfoMapper.updateUserAge(1L, 22);
            userInfoMapper.queryAllUser().forEach(System.out::println);
            System.out.println("---------------------");

            System.out.println("delete user 1L");
            userInfoMapper.delUserById(1L);
            userInfoMapper.queryAllUser().forEach(System.out::println);
        } finally {
            sqlSession.close();
        }
    }
}

运行结果:

说明通过Mybatis,我们也成功完成了对数据库的增删改查。不难发现,使用Mybatis后,简化了JDBC操作,我们可以更关注SQL本身,而不用重复去做一些连接获取、对象映射的操作。

参考链接:

1. Mybatis是什么以及Mybatis和JDBC的关系?

2. 什么是ORM

3. JDBC 使用说明

4. Mybatis之Mapper接口如何执行SQL

赞(1) 打赏
Zhuoli's Blog » Mybatis源码解读『Java操作Mysql』
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址