MyBatis—Spring 项目

目前大部分的 Java 互联网项目,都是用 Spring MVC + Spring + MyBatis 搭建平台的。

使用 Spring IoC 可以有效的管理各类的 Java 资源,达到即插即拔的功能;通过 Spring AOP 框架,数据库事务可以委托给 Spring 管理,消除很大一部分的事务代码,配合 MyBatis 的高灵活、可配置、可优化 SQL 等特性,完全可以构建高性能的大型网站。

毫无疑问,MyBatis 和 Spring 两大框架已经成了 Java 互联网技术主流框架组合,它们经受住了大数据量和大批量请求的考验,在互联网系统中得到了广泛的应用。使用 MyBatis-Spring 使得业务层和模型层得到了更好的分离,与此同时,在 Spring 环境中使用 MyBatis 也更加简单,节省了不少代码,甚至可以不用 SqlSessionFactory、 SqlSession 等对象,因为 MyBatis-Spring 为我们封装了它们。

摘自:《Java EE 互联网轻量级框架整合开发》

第一步:创建测试工程

第一步,首先在 IDEA 中新建一个名为【MybatisAndSpring】的 WebProject 工程:

然后在【src】中创建 4 个空包:

  • cn.wmyskxz.dao(放置 DAO 数据交互层处理类)
  • cn.wmyskxz.mapper(放置 Mapper 代理接口)
  • cn.wmyskxz.pojo(放置 Java 实体类)
  • cn.wmyskxz.test(放置测试类)

接着新建源文件夹【config】,用于放置各种资源配置文件:

  • 在【config / mybatis】下创建一个空的名为 “SqlMapConfig.xml” 的 MyBatis 全局配置文件
  • 在【config / spring】下创建一个空的名为 “applicationContext.xml” 的 Spring 资源配置文件
  • 在【config / sqlmap】下创建一个空的名为 “UserMapper.xml” 的 Mapper 映射文件。
  • 在【config】下创建两个 properties 属性文件,分别为 “db.properties” 和 “log4j.properties”,用于数据库连接和日志系统参数设置。

再在【web】文件夹下新建一个【WEB-INF】默认安全文件夹,并在其下创建一个【classes】和【lib】,并将项目的输出位置,改在【classes】下:

工程的完整初始结构如下:

第二步:引入依赖 jar 包

第二步,就是要准备项目的依赖 jar 包:

在【WEB-INF】文件夹下的【lib】文件夹中放置上面列举的 jar 包,然后添加依赖。

第三步:编写 Spring 配置文件

第三步,编写 Spring 的配置文件:

  • 加载数据库连接文件 “db.properties” 中的数据,建立数据源
  • 配置 sqlSessionFactory 会话工厂对象
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 加载配置文件 -->
    <context:property-placeholder location="classpath:db.properties"/>

    <!-- 配置数据源 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- sqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 加载 MyBatis 的配置文件 -->
        <property name="configLocation" value="mybatis/SqlMapConfig.xml"/>
        <!-- 数据源 -->
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>
  • 头部的信息就是声明 xml 文档配置标签的规则的限制与规范。
  • “context:property-placeholder” 配置是用于读取工程中的静态属性文件,然后在其他配置中使用时,就可以采用 “${属性名}” 的方式获取该属性文件中的配置参数值。
  • 配置了一个名为 “dataSrouce” 的 bean 的信息,实际上是连接数据库的数据源。
  • 设置 sqlSessionFactory 的 bean 实现类为 MyBatis 与 Spring 整合 jar 包中的 SqlSessionFactoryBean 类,在其中只需要注入两个参数:一个是 MyBatis 的全局配置文件,一个是上面配置的数据源 bean

第四步:编写 MyBatis 配置文件

第四步,在【mybatis】包下编写 MyBatis 的全局配置文件 SqlMapConfig.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>

    <!-- settings -->
    <settings>
        <!-- 打开延迟加载的开关 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 将积极加载改为消极加载(即按需加载) -->
        <setting name="aggressiveLazyLoading" value="false"/>
        <!-- 打开全局缓存开关(二级缓存)默认值就是 true -->
        <setting name="cacheEnabled" value="true"/>
    </settings>

    <!-- 别名定义 -->
    <typeAliases>
        <package name="cn.wmyskxz.pojo"/>
    </typeAliases>

    <!-- 加载映射文件 -->
    <mappers>
        <!-- 通过 resource 方法一次加载一个映射文件 -->
        <mapper resource="sqlmap/UserMapper.xml"/>
        <!-- 批量加载mapper -->
        <package name="cn.wmyskxz.mapper"/>
    </mappers>
</configuration>

在该配置文件中:

  • 通过 settings 配置了一些延迟加载和缓存的开关信息
  • typeAliases 中设置了一个 package 的别名扫描路径,在该路径下的 Java 实体类都可以拥有一个别名(即首字母小写的类名)
  • 在 mappers 配置中,使用 mapper 标签配置了即将要加载的 Mapper 映射文件的资源路径,当然也可以使用 package 标签,配置 mapper 代理接口所在的包名,以批量加载 mapper 代理对象。
  • 注意: 有了 Spring 托管数据源,在 MyBatis 配置文件中仅仅需要关注性能化配置。

第五步:编写 Mapper 以及其他配置文件

第五步,编写 Mapper 映射文件,这里依然定义 Mapper 映射文件的名字为 “UserMapper.xml” (与 SqlMapConfig.xml 中配置一致),为了测试效果,只配置了一个查询类 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">
<mapper namespace="test">
    <select id="findUserById" parameterType="_int" resultType="user">
    SELECT * FROM USER WHERE id = #{id}
</select>
</mapper>

在该配置中,输出参数的映射为 “user” ,这是因为之前在 SqlMapConfig.xml 中配置了 “cn.wmyskxz.pojo” 包下的实体类使用别名(即首字母小写的类名),所以这里只需在 “cn.wmyskxz.pojo” 包下,创建 “finduserById” 对应的 Java 实体类 User:

package cn.wmyskxz.pojo;

import java.io.Serializable;

public class User implements Serializable {
    private int id;
    private String username;

    /* getter and setter */
}
  • 实现 Serializable 接口是为之后使用 Mapper 动态代理做准备,这里没有使用动态代理。

在数据库资源 “db.properties” 中配置了数据库的连接信息,以 “key=value” 的形式配置,String 正是使用 “${}” 获取其中 key 对应的 value 配置的:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=UTF-8
jdbc.username=root
jdbc.password=root

另外日志配置和之前的配置一样,我就直接黏贴了:

# Global logging configuration
# 在开发环境下日志级别要设置成 DEBUG ,生产环境设为 INFO 或 ERROR
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

第六步:编写 DAO 层

第六步,进行数据库交互(Data Access Object)层的编写。

由于该项目只对 User 用户查询,所以 DAO 层就只有一个类,在 “cn.wmyskxz” 包下创建 DAO 层的 interface 接口,其中定义了 findUserById 方法,参数为用户的 id 值(int 类型):

package cn.wmyskxz.dao;

import cn.wmyskxz.pojo.User;

public interface UserDAO {

    // 根据 id 查询用户信息
    public User findUserById(int id) throws Exception;
}

然后在同一个包下创建 UserDAO 接口的实现类 UserDAOImpl:

package cn.wmyskxz.dao;

import cn.wmyskxz.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.support.SqlSessionDaoSupport;

public class UserDAOImpl extends SqlSessionDaoSupport implements UserDAO {

    @Override
    public User findUserById(int id) throws Exception {
        // 继承 SqlSessionDaoSupport 类,通过 this.getSqlSession() 得到 sqlSession
        SqlSession sqlSession = this.getSqlSession();
        User user = sqlSession.selectOne("test.findUserById", id);
        return user;
    }
}
  • 有几点解释:
  • UserDAOImpl 不仅实现了 UserDAO 接口,而且继承了 SqlSessionDaoSupport 类。
  • SqlSessionDaoSupport 类是 MyBatis 与 Spring 整合的 jar 包中提供的,在该类中已经包含了 sqlSessionFactory 对象作为其成员变量,而且对外提供 get 和 set 方法,方便 Spring 从外部注入 sqlSessionFactory 对象。
  • UserDAOImpl 类要成功获取 sqlSessionFactory 对象,还需要在 Spring 配置文件 applicationContext.xml 中添加 userDAO 的 bean 配置,将其中定义的 sqlSessionFactory 对象当做参数注入进去,这样 UserDAOImpl 继承 SqlSessionDaoSupport 类才会起到作用:
<!-- 原始 DAO 接口 -->
<bean id="userDAO" class="cn.wmyskxz.dao.UserDAOImpl">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
  • 注意: DAO 实现类继承了 SqlSessionDaoSupport 父类后,就无须自己定义获取 SqlSession 会话实例类方法了,该父类会默认加载数据源信息并提供获取 SqlSession 类的方法。

第七步:编写 Service 测试类

在 “cn.wmyskxz.test” 包下创建【UserServiceTest】测试类:

package cn.wmyskxz.test;

import cn.wmyskxz.dao.UserDAO;
import cn.wmyskxz.pojo.User;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserServiceTest {

    private ApplicationContext applicationContext;

    // 在执行测试方法之前首先获取 Spring 配置文件对象
    // 注解@Before在执行本类所有测试方法之前先调用这个方法
    @Before
    public void setup() throws Exception {
        applicationContext = new
                ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml");
    }

    @Test
    public void testFindUserById() throws Exception {
        // 通过配置资源对象获取 userDAO 对象
        UserDAO userDAO = (UserDAO) applicationContext.getBean("userDAO");
        // 调用 UserDAO 的方法
        User user = userDAO.findUserById(1);
        // 输出用户信息
        System.out.println(user.getId() + ":" + user.getUsername());
    }
}

运行测试方法,输出结果如下:


动态代理 + 注解实现

上面的实例程序并没有使用 Mapper 动态代理和注解来完成,下面我们就来试试如何用动态代理和注解:

第一步:编写 UserQueryMapper

在【mapper】下新建一个【UserQueryMapper】代理接口,并使用注解:

package cn.wmyskxz.mapper;

import cn.wmyskxz.pojo.User;
import org.apache.ibatis.annotations.Select;

public interface UserQueryMapper {

    @Select("SELECT * FROM USER WHERE id = #{id}")
    public User findUserById(int id) throws Exception;
}
  • 注意: 在默认情况下,该 bean 的名字为 userQueryMapper(即首字母小写)

现在有了代理类,我们需要通知 Spring 在这里来扫描到该类,Mapper 扫描配置对象需要用专门的扫描器:

<!-- Mapper 扫描器 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <!-- 扫描 cn.wmyskxz.mapper 包下的组件 -->
    <property name="basePackage" value="cn.wmyskxz.mapper"/>
</bean>

第二步:编写测试类

这一次我们获取的不再是 userDAO 对象,而是定义的 Mapper 代理对象 userQueryMapper:

package cn.wmyskxz.test;

import cn.wmyskxz.mapper.UserQueryMapper;
import cn.wmyskxz.pojo.User;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserServiceTest {

    private ApplicationContext applicationContext;

    // 在执行测试方法之前首先获取 Spring 配置文件对象
    // 注解@Before在执行本类所有测试方法之前先调用这个方法
    @Before
    public void setup() throws Exception {
        applicationContext = new
                ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml");
    }

    @Test
    public void testFindUserById() throws Exception {
        // 通过配置资源对象获取 userDAO 对象
        UserQueryMapper userQueryMapper = (UserQueryMapper) applicationContext.getBean("userQueryMapper");
        // 调用 UserDAO 的方法
        User user = userQueryMapper.findUserById(1);
        // 输出用户信息
        System.out.println(user.getId() + ":" + user.getUsername());
    }
}

运行测试方法,得到正确结果:

可以看到,查询结果和之前非 Mapper 代理的查询结果一样。

  • 原理: 在 applicationContext.xml 配置文件中配置的 mapper 批量扫描器类,会从 mapper 包中扫描出 Mapper 接口,自动创建代理对象并且在 Spring 容器中注入。自动扫描出来的 Mapper 的 bean 的 id 为 mapper 类名(首字母小写),所以这里获取的就是名为 “userQueryMapper” 的 mapper 代理对象。

参考资料:

  • 《Java EE 互联网轻量级框架整合开发》
  • 《Spring MVC + MyBatis开发从入门到项目实战》
  • 全能的百度和万能的大脑

欢迎转载,转载请注明出处!
简书ID:@我没有三颗心脏
github:wmyskxz
欢迎关注公众微信号:wmyskxz_javaweb
分享自己的Java Web学习之路以及各种Java学习资料

编写日志输出环境配置文件

在开发过程中,最重要的就是在控制台查看程序输出的日志信息,在这里我们选择使用 log4j 工具来输出:

  • 准备工作: 将【MyBatis】文件夹下【lib】中的 log4j 开头的 jar 包都导入工程并添加依赖。
    在【src】下新建一个文件 log4j.properties 资源:
# Global logging configuration
# 在开发环境下日志级别要设置成 DEBUG ,生产环境设为 INFO 或 ERROR
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

其中,第一条配置语句 “log4j.rootLogger=DEBUG, stdout” 指的是日志输出级别,一共有 7 个级别(OFF、 FATAL、 ERROR、 WARN、 INFO、 DEBUG、 ALL)。

  • 一般常用的日志输出级别分别为 DEBUG、 INFO、 ERROR 以及 WARN,分别表示 “调试级别”、 “标准信息级别”、 “错误级别”、 “异常级别”。如果需要查看程序运行的详细步骤信息,一般选择 “DEBUG” 级别,因为该级别在程序运行期间,会在控制台才打印出底层的运行信息,以及在程序中使用 Log 对象打印出调试信息。
  • 如果是日常的运行,选择 “INFO” 级别,该级别会在控制台打印出程序运行的主要步骤信息。“ERROR” 和 “WARN” 级别分别代表 “不影响程序运行的错误事件” 和 “潜在的错误情形”。
  • 文件中 “stdout” 这段配置的意思就是将 DEBUG 的日志信息输出到 stdout 参数所指定的输出载体中。

第二条配置语句 “log4j.appender.stdout=org.apache.log4j.ConsoleAppender” 的含义是,设置名为 stdout 的输出端载体是哪种类型。

  • 目前输出载体有
    ConsoleAppender(控制台)
    FileAppender(文件)
    DailyRollingFileAppender(每天产生一个日志文件)
    RollingFileAppender(文件大小到达指定大小时产生一个新的文件)
    WriterAppender(将日志信息以流格式发送到任意指定的地方)
  • 这里要将日志打印到 IDEA 的控制台,所以选择 ConsoleAppender

第三条配置语句 “log4j.appender.stdout.layout=org.apache.log4j.PatternLayout” 的含义是,名为 stdout 的输出载体的 layout(即界面布局)是哪种类型。

  • 目前输出端的界面类型分为
    HTMLLayout(以 HTML 表格形式布局)
    PatternLayout(可以灵活地指定布局模式)
    SimpleLayout(包含日志信息的级别和信息字符串)
    TTCCLayout(包含日志产生的时间、线程、类别等信息)
  • 这里选择灵活指定其布局类型,即自己去配置布局。

第四条配置语句 “log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n” 的含义是,如果 layout 界面布局选择了 PatternLayout 灵活布局类型,要指定的打印信息的具体格式。

  • 格式信息配置元素大致如下:
    %m 输出代码中的指定的信息
    %p 输出优先级,即 DEBUG、 INFO、 WARN、 ERROR 和 FATAL
    %r 输出自应用启动到输出该 log 信息耗费的毫秒数
    %c 输出所属的类目,通常就是所在类的全名
    %t 输出产生该日志事件的线程名
    %n 输出一个回车换行符,Windows 平台为 “rn”,UNIX 平台为 “n
    %d 输出日志时的时间或日期,默认个事为 ISO8601,也可以在其后指定格式,比如 %d{yyy MMM dd HH:mm:ss},输出类似:2018 年 4 月18 日 10:32:00
    %l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数

MyBatis 高级映射

上一篇文章中,我们讲解了一个 MyBatis 的入门程序的开发,了解了 MyBatis 开发的基本内容。今天我们先来了解一下 MyBatis 是如何处理多张数据库表之间的关联关系,其中包括:

  • 一对一的查询
  • 一对多查询
  • 多对多查询
  • 延迟加载

一对一查询

首先我们先来建立一个数据模型(删掉之前创建的 student 表):

use mybatis;
CREATE TABLE student (
  id int(11) NOT NULL AUTO_INCREMENT,
  name varchar(255) DEFAULT NULL,
  card_id int(11) NOT NULL,
  PRIMARY KEY (id)
)AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE card (
  id int(11) NOT NULL AUTO_INCREMENT,
  number int(11)  NOT NULL,
  PRIMARY KEY (id)
)AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

INSERT INTO student VALUES (1,'student1',1);
INSERT INTO student VALUES (2,'student2',2);

INSERT INTO card VALUES (1,1111);
INSERT INTO card VALUES (2,2222);
  • 注意: 这里并没有在数据库中设置外键,而是让 MyBatis 去处理多表之间的关系。事实上,外键只是用来保证数据一致性,在某些特殊的情况下(例如高并发秒杀系统中),会专门设置不适用外键,因为存在一定的性能损耗。

然后我们要来确认我们查询的 SQL 语句,我们或许可以简单的写成下面这样:

SELECT
    student.*,
    card.*
FROM
    student,card
WHERE student.card_id = card.id AND card.number = #{value}
  • 提示: 在日常开发中,总是先确定业务的具体 SQL ,再将此 SQL 配置在 Mapper 文件中

确定了主要的查询 SQL 后,接下来我们分别使用 resultType 和 resultMap 来实现这个一对一查询的实例。

1. 使用 resultType 实现

首先创建学生 student 表所对应的 Java 实体类 Student,其中封装的属性信息为响应数据库中的字段:

package pojo;

public class Student {

    int id;
    String name;
    int card_id;

    /* getter and setter */
}

最终我们执行查询(上述的 SQL 语句)的结果如下:

由于最终的查询的结果是由 resultType 指定的,也就是只能映射一个确定的 Java 包装类,上面的 Stuent 类只包含了学生的基本信息,并没有包含 Card 的信息,所以我们要创建一个最终映射类,以 Student 类为父类,然后追加 Card 的信息:

package pojo;

public class StudentAndCard extends Student {
    private int number;

    /* getter and setter /*
}

然后在 Student.xml 映射文件中定义 <select> 类型的查询语句 SQL 配置,将之前设计好的 SQL 语句配置进去,然后指定输出参数属性为 resultType,类型为 StudentAndCard 这个 Java 包装类:

<select id="findStudentByCard" parameterType="_int" resultType="Student">
  SELECT
    student.*,
    card.*
  FROM
    student,card
  WHERE student.card_id = card.id AND card.number = #{value}
</select>

然后在测试类中编写测试方法:

@Test
public void test() throws IOException {

    // 根据 mybatis-config.xml 配置的信息得到 sqlSessionFactory
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    // 然后根据 sqlSessionFactory 得到 session
    SqlSession session = sqlSessionFactory.openSession();

    // 找到身份证身份证号码为 1111 的学生
    StudentAndCard student = session.selectOne("findStudentByCard",1111);
    // 获得其姓名并输出
    System.out.println(student.getName());
}

获得正确结果:

2. 使用 resultMap 实现

使用 resultMap 可以将数据字段映射到名称不一样的响应实体类属性上,重要的是,可以映射实体类中包裹的其他实体类。

首先我们来创建一个封装了 Card 号码和 Student 实体类的 StudentWithCard 类:

package pojo;

public class StudentWithCard {

    Student student;
    int number;
    int id;

    /* getter and setter */
}

SQL 语句依然没有变化,但是使用的输出映射属性改为了 resultMap ,其中的映射类型是 id 为 StudentInfoMap 的 resultMap 配置:

<select id="findStudentByCard" parameterType="_int" resultMap="StudentInfoMap">
  SELECT
    student.*,
    card.*
  FROM
    student,card
  WHERE student.card_id = card.id AND card.number = #{value}
</select>

<resultMap id="StudentInfoMap" type="pojo.StudentWithCard">
    <!-- id 标签表示对应的主键
         column 对应查询结果的列值
         property 对应封装类中的属性名称
         -->
    <id column="id" property="id"/>
    <result column="number" property="number"/>
    <!-- association 表示关联的嵌套结果,
         可以简单理解就是为封装类指定的标签 
         -->
    <association property="student" javaType="pojo.Student">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="card_id" property="card_id"/>
    </association>
</resultMap>

稍微修改一下测试类,测试使用 resultMap 实现的一对一查询映射:

@Test
public void test() throws IOException {

    // 根据 mybatis-config.xml 配置的信息得到 sqlSessionFactory
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    // 然后根据 sqlSessionFactory 得到 session
    SqlSession session = sqlSessionFactory.openSession();

    // 找到身份证身份证号码为 1111 的学生
    StudentWithCard student = session.selectOne("findStudentByCard", 1111);
    // 获得其姓名并输出
    System.out.println(student.getStudent().getName());
}

测试仍然能得到正确的结果:

一对多查询

还是先来建立数据模型,删掉之前的:

use mybatis;
CREATE TABLE student (
  student_id int(11) NOT NULL AUTO_INCREMENT,
  name varchar(255) DEFAULT NULL,
  PRIMARY KEY (student_id)
)AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE class (
  class_id int(11) NOT NULL AUTO_INCREMENT,
  name varchar(255) NOT NULL,
  student_id int(11)  NOT NULL,
  PRIMARY KEY (class_id)
)AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

INSERT INTO student VALUES (1,'student1');
INSERT INTO student VALUES (2,'student2');

INSERT INTO class VALUES (1,'Java课',1);
INSERT INTO class VALUES (2,'Java课',2);
  • 其中 class 的 name 字段表示课程的名称。

然后我们来编写我们的 SQL 语句:

SELECT 
  student.*
FROM
  student, class
WHERE student.student_id = class.student_id AND class.class_id = #{value}

我们执行的结果如下:

图片.png

我们再来创建对应的实体类:

public class Student {

    private int id;
    private String name;

    /* getter and setter */
}

public class Class {

    private int id;
    private String name;
    private List<Student> students;

    /* getter and setter */
}

在 Package【pojo】下新建一个【class.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="class">
    <resultMap id="Students" type="pojo.Student">
        <id column="student_id" property="id"/>
        <result column="name" property="name"/>
    </resultMap>
    <select id="listStudentByClassName" parameterType="String" resultMap="Students">
        SELECT
          student.*
        FROM
          student, class
        WHERE student.student_id = class.student_id AND class.name= #{value}
    </select>
</mapper>

编写测试类:

@Test
public void test() throws IOException {

    // 根据 mybatis-config.xml 配置的信息得到 sqlSessionFactory
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    // 然后根据 sqlSessionFactory 得到 session
    SqlSession session = sqlSessionFactory.openSession();

    // 查询上Java课的全部学生
    List<Student> students = session.selectList("listStudentByClassName", "Java课");
    for (Student student : students) {
        System.out.println("ID:" + student.getId() + ",NAME:" + student.getName());
    }
}

运行测试结果,成功:

多对多查询

建立数据模型:

use mybatis;
CREATE TABLE students (
  student_id int(11) NOT NULL AUTO_INCREMENT,
  student_name varchar(255) DEFAULT NULL,
  PRIMARY KEY (student_id)
)AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE courses (
  course_id int(11) NOT NULL AUTO_INCREMENT,
  course_name varchar(255) NOT NULL,
  PRIMARY KEY (course_id)
)AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE student_select_course(
  s_id int(11) NOT NULL,
  c_id int(11) NOT NULL,
  PRIMARY KEY(s_id,c_id)
) DEFAULT CHARSET=utf8;

INSERT INTO students VALUES (1,'student1');
INSERT INTO students VALUES (2,'student2');

INSERT INTO courses VALUES (1,'Java课');
INSERT INTO courses VALUES (2,'Java Web课');

INSERT INTO student_select_course VALUES(1,1);
INSERT INTO student_select_course VALUES(1,2);
INSERT INTO student_select_course VALUES(2,1);
INSERT INTO student_select_course VALUES(2,2);

根据要求我们来设计一下 SQL 语言:

SELECT
    s.student_id,s.student_name
FROM
    students s,student_select_course ssc,courses c
WHERE s.student_id = ssc.s_id 
AND ssc.c_id = c.course_id 
AND c.course_name = #{value}

执行 SQL 结果如下:

实体类雷同,就不再赘述,我们直接来配置映射文件【Student.xml】:

<resultMap id="Students" type="pojo.Student">
    <id property="id" column="student_id"/>
    <result column="student_name" property="name"/>
</resultMap>

<select id="findStudentsByCourseName" parameterType="String" resultMap="Students">
    SELECT
      s.student_id,s.student_name
    FROM
      students s,student_select_course ssc,courses c
    WHERE s.student_id = ssc.s_id
    AND ssc.c_id = c.course_id
    AND c.course_name = #{value}
</select>

测试类也雷同,只需要修改一下调用的 id (改为findStudentsByCourseName)就好了,直接上测试结果:

相反也是一样的,重要的是 SQL 语句和映射。

总结:

  • 自己写的 SQL 语句看着虽然没有很恶心(至少思路清晰),但感觉很烂!
  • 结合 SQL 语言和映射文件,能够很方便的操作数据库
  • 数据库还是建立外键得好….(啪啪打脸,根据《阿里Java开发手册》里提到,最好不要建外键,而让程序的Service层去做判断)

延迟加载

什么是延迟加载?从字面上理解,就是对某一类信息的加载之前需要延迟一会儿。在 MyBatis 中,通常会进行多表联合查询,但是有的时候不会立即用到所有的联合查询结果,这时候就可以采用延迟加载的功能。

  • 功能: 延迟加载可以做到,先从单表查询,需要时再从关联表关联查询,这样就大大提高了数据库的性能,因为查询单表要比关联查询多张表速度快。
  • 实例: 如果查询订单并且关联查询用户信息。如果先查询订单信息即可满足要求,当我们需要查询用户信息时再查询用户信息。把对用户信息的按需去查询就是延迟加载。
关联查询:
SELECT 
    orders.*, user.username 
FROM orders, user
WHERE orders.user_id = user.id
延迟加载相当于:
SELECT 
    orders.*,
    (SELECT username FROM USER WHERE orders.user_id = user.id)
    username 
FROM orders

所以这就比较直观了,也就是说,我把关联查询分两次来做,而不是一次性查出所有的。第一步只查询单表orders,必然会查出orders中的一个user_id字段,然后我再根据这个user_id查user表,也是单表查询。

参考文章: 【MyBatis学习11】MyBatis中的延迟加载

Mapper 映射配置编写

首先在 Mapper 映射文件中定义只查询所有订单信息的 SQL :

<select id="findOrdersUserLazyLoading" resultMap="OrdersUserLazyLoadingResultMap">
    SELECT * FROM orders
</select>

上面的 SQL 语句查询所有的订单信息,而每个订单信息中会关联查询用户,但由于希望延迟加载用户信息,所以会在 id 为 “OrdersUserLazyLoadingResultMap“ 的 resultMap 对应的结果集配置中进行配置:

最后配置延迟加载要执行的获取用户信息的 SQL:

<select id="findUserById" parameterType="int" resultType="user">
    select * from user where id = #{id}
</select>

上面的配置会被用来延迟加载的 resultMap 中的 association 调用,输入参数就是 association 中 column 中定义的字段信息。

在编写测试方法之前,首先需要开启延迟加载功能(这在 MyBatis 中默认是禁用掉的)。这需要在 MyBatis 的全局配置文件 mybatis-config.xml 中配置 setting 属性,将延迟加载(lazyLoadingEnable)的开关设置成 “ture” ,并且由于是按需加载,所以还需要将积极加载改为消极加载:

<settings>
    <!-- 打开延迟加载的开关 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!-- 将积极加载改为消极加载,即延迟加载 -->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>
  • 注意: 在 configuration 中配置是有一定顺序的,具体可以按住【Ctrl】不放点击 configuration 属性,能看到如下信息(即定义的顺序):
<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>

Mapper 动态代理

什么是 Mapper 动态代理?一般创建 Web 工程时,从数据库取数据的逻辑会放置在 DAO 层(Date Access Object,数据访问对象)。使用 MyBatis 开发 Web 工程时,通过 Mapper 动态代理机制,可以只编写数据交互的接口及方法定义,和对应的 Mapper 映射文件,具体的交互方法实现由 MyBatis 来完成。这样大大节省了开发 DAO 层的时间。

实现 Mapper 代理的方法并不难,只需要遵循一定的开发规范即可。

Mapper 代理实例编写

我们编写一个使用 Mapper 代理查询学生信息的示例,首先还是在【pojo】下新建一个名为 StudentMapper.xml 的 Mapper 配置文件,其中包含了对 Student 的增删改查的 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">

<mapper namespace="mapper.StudentMapper">
    <!-- 查询学生 -->
    <select id="findStudentById" parameterType="_int" resultType="pojo.Student">
        SELECT * FROM student WHERE student_id = #{id}
    </select>
    <!-- 增加用户 -->
    <insert id="insertStudent" parameterType="pojo.Student">
        INSERT INTO student(student_id, name) VALUES(#{id}, #{name})
    </insert>
    <!-- 删除用户 -->
    <delete id="deleteStudent" parameterType="_int">
        DELETE FROM student WHERE student_id = #{id}
    </delete>
    <!-- 修改用户 -->
    <update id="updateStudent" parameterType="pojo.Student">
        UPDATE student SET name = #{name} WHERE student_id = #{id}
    </update>
</mapper>

如果需要使用 StudentMapper.xml 的 Mapper 代理,首先需要定义一个接口,名为 StudentMapper。然后在里面新建四个方法定义,分别对应 StudentMapper.xml 中的 Student 的增删改查的 SQL 配置,然后将 StudentMapper 中的 namespace 改为 StudentMapper 接口定义的地方(也就是 mapper 包下的 StudentMapper),这样就可以在业务类中使用 Mapper 代理了,接口代码如下:

package mapper;

import pojo.Student;

public interface StudentMapper {

    // 根据 id 查询学生信息
    public Student findStudentById(int id) throws Exception;

    // 添加学生信息
    public void insertStudent(Student student) throws Exception;

    // 删除学生信息
    public void deleteStudent(int id) throws Exception;

    // 修改学生信息
    public void updateStudent(Student student) throws Exception;
}
  • 注意: 别忘了在 mybatis-config.xml 中配置一下 Mapper 映射文件

测试动态代理

在测试方法中,使用 SqlSession 类的 getMapper 方法,并将要加载的 Mapper 代理的接口类传递进去,就可以获得相关的 Mapper 代理对象,使用 Mapper 代理对象去对学生信息进行增删改查:

@Test
public void test() throws Exception {

    // 根据 mybatis-config.xml 配置的信息得到 sqlSessionFactory
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    // 然后根据 sqlSessionFactory 得到 session
    SqlSession session = sqlSessionFactory.openSession();
    // 获取 Mapper 代理
    StudentMapper studentMapper = session.getMapper(StudentMapper.class);
    // 执行 Mapper 代理独享的查询方法
    Student student = studentMapper.findStudentById(1);
    System.out.println("学生的姓名为:" + student.getName());
    session.close();
}

运行测试方法,看到正确的结果:

使用 Mapper 代理可以让开发更加简洁,使查询结构更加清晰,工程结构更加规范。


使用注解开发 MyBatis

在上面的例子中,我们已经有了方便的 Mapper 代理对象,我们可以进一步省掉 XML 的配置信息,进而使用方便的注解来开发 MyBatis ,让我们实际来操练一下:

第一步:为 Mapper 增加注解

我们把 StudentMapper.xml 下配置的 SQL 语句通过注解的方式原封不动的配置在 StudentMapper 接口中:

public interface StudentMapper {

    // 根据 id 查询学生信息
    @Select("SELECT * FROM student WHERE student_id = #{id}")
    public Student findStudentById(int id) throws Exception;

    // 添加学生信息
    @Insert("INSERT INTO student(student_id, name) VALUES(#{id}, #{name})")
    public void insertStudent(Student student) throws Exception;

    // 删除学生信息
    @Delete("DELETE FROM student WHERE student_id = #{id}")
    public void deleteStudent(int id) throws Exception;

    // 修改学生信息
    @Update("UPDATE student SET name = #{name} WHERE student_id = #{id}")
    public void updateStudent(Student student) throws Exception;
}

第二步:修改 mybatis-config.xml

将之前配置的映射注释掉,新建一条:

<!-- 映射文件 -->
<mappers>
    <!--<mapper resource="pojo/StudentMapper.xml"/>-->
    <mapper class="mapper.StudentMapper"/>
</mappers>
  • 注意: 这次映射的并不是文件(使用 resource 属性),而是类(使用 class 属性)

第三步:运行测试代码

上面的测试代码不用修改,直接运行,也能得到正确结果:

更多的注解:戳这里


MyBatis 缓存结构

在 Web 系统中,最重要的操作就是查询数据库中的数据。但是有些时候查询数据的频率非常高,这是很耗费数据库资源的,往往会导致数据库查询效率极低,影响客户的操作体验。于是我们可以将一些变动不大且访问频率高的数据,放置在一个缓存容器中,用户下一次查询时就从缓存容器中获取结果。

  • MyBatis 拥有自己的缓存结构,可以用来缓解数据库压力,加快查询速度。
  • mybatis一级缓存是一个SqlSession级别,sqlsession只能访问自己的一级缓存的数据
  • 二级缓存是跨sqlSession,是mapper级别的缓存,对于mapper级别的缓存不同的sqlsession是可以共享的。

一级查询缓存

一级查询存在于每一个 SqlSession 类的实例对象中,当第一次查询某一个数据时,SqlSession 类的实例对象会将该数据存入一级缓存区域,在没有收到改变该数据的请求之前,用户再次查询该数据,都会从缓存中获取该数据,而不是再次连接数据库进行查询。

  • MyBatis 的一级缓存原理:

第一次发出一个查询 sql,sql 查询结果写入 sqlsession 的一级缓存中,缓存使用的数据结构是一个 map

  • key:hashcode+sql+sql输入参数+输出参数(sql的唯一标识)
  • value:用户信息

同一个 sqlsession 再次发出相同的 sql,就从缓存中取不走数据库。如果两次中间出现 commit 操作(修改、添加、删除),本 sqlsession 中的一级缓存区域全部清空,下次再去缓存中查询不到所以要从数据库查询,从数据库查询到再写入缓存。

一级缓存示例

  • 我们在同一个 session 中查询两次 id = 1 的 Category 对象试一试:

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 session1 = sqlSessionFactory.openSession();

    Category c1 = session1.selectOne("getCategory", 1);
    System.out.println(c1);
    Category c2 = session1.selectOne("getCategory", 1);
    System.out.println(c2);

    session1.commit();
    session1.close();

}

运行,可以看到第一次会去数据库中取数据,但是第二次就不会访问数据库了,而是直接从session中取出来:

  • 我们再来测试一下在不同 session 里查询相同的 id 数据
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 session1 = sqlSessionFactory.openSession();

    Category c1 = session1.selectOne("getCategory", 1);
    System.out.println(c1);
    Category c2 = session1.selectOne("getCategory", 1);
    System.out.println(c2);

    session1.commit();
    session1.close();

    SqlSession session2 = sqlSessionFactory.openSession();
    Category c3 = session2.selectOne("getCategory", 1);
    System.out.println(c3);
    session2.commit();
    session2.close();

}

这一次,另外打开一个 session , 取同样 id 的数据,就会发现需要执行 sql 语句,证实了一级缓存是在 session 里的:

MyBatis 一级缓存值得注意的地方:

  • MyBatis 默认就是支持一级缓存的,并不需要我们配置.
  • MyBatis 和 spring 整合后进行 mapper 代理开发,不支持一级缓存,mybatis和 spring 整合,spring 按照 mapper 的模板去生成 mapper 代理对象,模板中在最后统一关闭 sqlsession。

二级查询缓存

  • 问题: 有些时候,在 Web 工程中会将执行查询操作的方法封装在某个 Service 方法中,当查询完一次后,Service 方法结束,此时 SqlSession 类的实例对象就会关闭,一级缓存就会被清空。
  • 二级缓存原理:

二级缓存的范围是 mapper 级别(mapper即同一个命名空间),mapper 以命名空间为单位创建缓存数据结构,结构是 map。

要开启二级缓存,需要进行两步操作。

第一步:在 MyBatis 的全局配置文件 mybatis-config.xml 中配置 setting 属性,设置名为 “cacheEnable” 的属性值为 “true” 即可:

<settings>
    <!-- 开启二级缓存 -->
    <setting name="cacheEnabled" value="true"/>
</settings>
  • 注意: settings 配置的位置一定是在 properties 后面,typeAliases前面!

第二步:然后由于二级缓存是 Mapper 级别的,还要在需要开启二级缓存的具体 mapper.xml 文件中开启二级缓存,只需要在相应的 mapper.xml 中添加一个 cache 标签即可:

<!-- 开启本 Mapper 的 namespace 下的二级缓存 -->
<cache />

开启二级缓存之后,我们需要为查询结果映射的 POJO 类实现 java.io.serializable 接口,二级缓存可以将内存的数据写到磁盘,存在对象的序列化和反序列化,所以要实现java.io.serializable接口。

二级缓存示例

我们在同一个 SessionFactory 下查询 id = 1 的数据,只有第一次需要执行 SQL 语句,从后都是从缓存中取出来的:

参考资料:how2j.cn-MyBatis教程Java3y-Mybatis【缓存、代理、逆向工程】

参考资料:

  • 《Java EE 互联网轻量级框架整合开发》
  • 《Spring MVC + MyBatis开发从入门到项目实战》
  • How2j-MyBatis 系列教程
  • 全能的百度和万能的大脑

欢迎转载,转载请注明出处!
简书ID:@我没有三颗心脏
github:wmyskxz
欢迎关注公众微信号:wmyskxz_javaweb
分享自己的Java Web学习之路以及各种Java学习资料

MyBatis 简介

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis,是一个基于Java的持久层框架。

  • 持久层: 可以将业务数据存储到磁盘,具备长期存储能力,只要磁盘不损坏,在断电或者其他情况下,重新开启系统仍然可以读取到这些数据。
  • 优点: 可以使用巨大的磁盘空间存储相当量的数据,并且很廉价
  • 缺点:慢(相对于内存而言)

为什么使用 MyBatis

在我们传统的 JDBC 中,我们除了需要自己提供 SQL 外,还必须操作 Connection、Statment、ResultSet,不仅如此,为了访问不同的表,不同字段的数据,我们需要些很多雷同模板化的代码,闲的繁琐又枯燥

而我们在使用了 MyBatis 之后,只需要提供 SQL 语句就好了,其余的诸如:建立连接、操作 Statment、ResultSet,处理 JDBC 相关异常等等都可以交给 MyBatis 去处理,我们的关注点于是可以就此集中在 SQL 语句上,关注在增删改查这些操作层面上。

并且 MyBatis 支持使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。


搭建 MyBatis 环境

首先,我们需要先下载和搭建 MyBatis 的开发环境。

下载 MyBatis 工程包

打开链接 http://github.com/mybatis/mybatis-3/releases 下载 MyBatis 所需要的包和源码,当前最新版本为 3.4.6,官方还提供了文档: 戳这里,虽然感觉写得一般,但还是有一些参考价值…唉,别当教程看,当字典看!

下载好 MyBatis 的包解压后,可以得到以下的文件目录:

其中 mybatis-3.4.6.jar 包就是 MyBatis 的项目工程包,【lib】文件夹下就是 MyBatis 项目需要依赖的第三方包,pdf 文件是它英文版的说明,不要英文也可以戳上面的链接。

为 IDEA 配置 MyBatis 环境

IDEA 默认是不支持 MyBatis 开发的,需要自己下载第三方插件来支持,可惜的是功能强大的【MyBatis Plugin】是收费的,需要我们自己破解!

第一步:在 IDEA 中下载 MyBatis Plugin

在【File】菜单下找到【Settings】,然后再【Plugins】下点击【Browse repositories..】:

在搜索栏中输入【MyBatis Plugin】,然后点击【Install】(我这里是安装好了所以没有这个按钮):

第二步:破解

有幸找到最新的破解方法,最新支持破解的版本号为:v3.58 crack,下载链接:戳这里

把它下载到 【D:\Download\】目录下,打开 idea.vmoptions (【Help】-> 【Eidt Custom VM Options…】):
在下方插入 -javaagent:D:/Download/ideaagent-1.2.jar

重启 IDEA,首次启动需要信任本地服务器 ssl 证书,点击接受后如未激活,再次重启即可:

至此,我们就为 IDEA 配置好了 MyBatis 的开发环境,可以检验一下是否安装成功:


第一个 MyBatis 程序

我们来实际开发一个 MyBatis 程序,感受一下。

第一步:准备数据库

首先我们创建一个数据库【mybatis】,编码方式设置为 UTF-8,然后再创建一个名为【student】的表,插入几行数据:

DROP DATABASE IF EXISTS mybatis;
CREATE DATABASE mybatis DEFAULT CHARACTER SET utf8;

use mybatis;
CREATE TABLE student(
  id int(11) NOT NULL AUTO_INCREMENT,
  studentID int(11) NOT NULL UNIQUE,
  name varchar(255) NOT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO student VALUES(1,1,'我没有三颗心脏');
INSERT INTO student VALUES(2,2,'我没有三颗心脏');
INSERT INTO student VALUES(3,3,'我没有三颗心脏');

第二步:创建工程

在 IDEA 中新建一个 Java 工程,并命名为【HelloMybatis】,然后导入必要的 jar 包:

  • mybatis-3.4.6.jar
  • mysql-connector-java-5.1.21-bin.jar

第三步:创建实体类

在 Package【pojo】下新建实体类【Student】,用于映射表 student:

package pojo;

public class Student {

    int id;
    int studentID;
    String name;

    /* getter and setter */
}

第四步:配置文件 mybatis-config.xml

在【src】目录下创建 MyBaits 的主配置文件 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>

    <!-- 别名 -->
    <typeAliases>
        <package name="pojo"/>
    </typeAliases>
    <!-- 数据库环境 -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 映射文件 -->
    <mappers>
        <mapper resource="pojo/Student.xml"/>
    </mappers>

</configuration>

第五步:配置文件 Student.xml

在 Package【pojo】下新建一个【Student.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="pojo">
    <select id="listStudent" resultType="Student">
        select * from  student
    </select>
</mapper>
  • 由于上面配置了 <typeAliases> 别名,所以在这里的 resultType 可以直接写 Student,而不用写类的全限定名 pojo.Student
  • namespace 属性其实就是对 SQL 进行分类管理,实现不同业务的 SQL 隔离
  • SQL 语句的增删改查对应的标签有:

第六步:编写测试类

在 Package【test】小创建测试类【TestMyBatis】:

package test;

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 pojo.Student;

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

public class TestMyBatis {

    public static void main(String[] args) throws IOException {
        // 根据 mybatis-config.xml 配置的信息得到 sqlSessionFactory
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 然后根据 sqlSessionFactory 得到 session
        SqlSession session = sqlSessionFactory.openSession();
        // 最后通过 session 的 selectList() 方法调用 sql 语句 listStudent
        List<Student> listStudent = session.selectList("listStudent");
        for (Student student : listStudent) {
            System.out.println("ID:" + student.getId() + ",NAME:" + student.getName());
        }

    }
}

运行测试类:

基本原理

  • 应用程序找 MyBatis 要数据
  • MyBatis 从数据库中找来数据

1.通过 mybatis-config.xml 定位哪个数据库
2.通过 Student.xml 执行对应的 sql 语句
3.基于 Student.xml 把返回的数据库封装在 Student 对象中
4.把多个 Student 对象装载一个 Student 集合中

  • 返回一个 Student 集合

参考资料:How2j.cn-MyBatis 相关教程


CRUD 操作

我们来看看常规的一套增删改查应该怎么实现:

第一步:配置 Student.xml

首先,我们在 SQL 映射文件中新增语句,用来支撑 CRUD 的系列操作

<?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="pojo">
    <select id="listStudent" resultType="Student">
        select * from  student
    </select>

    <insert id="addStudent" parameterType="Student">
        insert into student (id, studentID, name) values (#{id},#{studentID},#{name})
    </insert>

    <delete id="deleteStudent" parameterType="Student">
        delete from student where id = #{id}
    </delete>

    <select id="getStudent" parameterType="_int" resultType="Student">
        select * from student where id= #{id}
    </select>

    <update id="updateStudent" parameterType="Student">
        update student set name=#{name} where id=#{id}
    </update>
</mapper>
  • parameterType:要求输入参数的类型
  • resultType:输出的类型

第二步:实现增删改查

package test;

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 pojo.Student;

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

public class TestMyBatis {

    public static void main(String[] args) throws IOException {
        // 根据 mybatis-config.xml 配置的信息得到 sqlSessionFactory
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 然后根据 sqlSessionFactory 得到 session
        SqlSession session = sqlSessionFactory.openSession();

        // 增加学生
        Student student1 = new Student();
        student1.setId(4);
        student1.setStudentID(4);
        student1.setName("新增加的学生");
        session.insert("addStudent", student1);

        // 删除学生
        Student student2 = new Student();
        student2.setId(1);
        session.delete("deleteStudent", student2);

        // 获取学生
        Student student3 = session.selectOne("getStudent", 2);

        // 修改学生
        student3.setName("修改的学生");
        session.update("updateStudent", student3);

        // 最后通过 session 的 selectList() 方法调用 sql 语句 listStudent
        List<Student> listStudent = session.selectList("listStudent");
        for (Student student : listStudent) {
            System.out.println("ID:" + student.getId() + ",NAME:" + student.getName());
        }

        // 提交修改
        session.commit();
        // 关闭 session
        session.close();
    }
}

上述的程序中:

  • 通过 session.insert("addStudent", student1); 增加了一个 ID 和 studentID 都为 4,名字为“新增加的学生” 的学生
  • 通过 session.delete("deleteStudent", student2); 删除了 ID = 1 的学生
  • 通过 Student student3 = session.selectOne("getStudent", 2); 获取了 ID = 2的学生
  • 通过 session.update("updateStudent", student3); 将 ID = 2 的学生的名字修改为 “修改的学生”
  • 通过 session.commit() 来提交事务,也可以简单理解为更新到数据库

运行获得正确结果:

模糊查询

如果要对数据库中的 student 表进行模糊查询,需要通过匹配名字中的某个字来查询该用户。

我们首先在 Student.xml 配置文件中配置 SQL 映射:

<select id="findStudentByName" parameterMap="java.lang.String" resultType="Student">
    SELECT * FROM student WHERE name LIKE '%${value}%' 
</select>
  • 注意: <select> 标签对中 SQL 语句的 “${}” 符号,表示拼接 SQL 串,将接受的参数内容不加任何修饰地拼接在 SQL 中,在 “${}” 中只能使用 value 来代表其中的参数。

因为是模糊查询,所以得到的查询结果可能不止一个,所以我们使用 SqlSession 的 selectList() 方法,写一个测试方法:

@Test
public void test() throws IOException {

    // 根据 mybatis-config.xml 配置的信息得到 sqlSessionFactory
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    // 然后根据 sqlSessionFactory 得到 session
    SqlSession session = sqlSessionFactory.openSession();

    // 模糊查询
    List<Student> students = session.selectList("findStudentByName", "三颗心脏");
    for (Student student : students) {
        System.out.println("ID:" + student.getId() + ",NAME:" + student.getName());
    }
}

测试结果:

总结一下

  • 关于 parameterType: 就是用来在 SQL 映射文件中指定输入参数类型的,可以指定为基本数据类型(如 int、float 等)、包装数据类型(如 String、Interger 等)以及用户自己编写的 JavaBean 封装类
  • 关于 resultType: 在加载 SQL 配置,并绑定指定输入参数和运行 SQL 之后,会得到数据库返回的响应结果,此时使用 resultType 就是用来指定数据库返回的信息对应的 Java 的数据类型。
  • 关于 “#{}” : 在传统的 JDBC 的编程中,占位符用 “?” 来表示,然后再加载 SQL 之前按照 “?” 的位置设置参数。而 “#{}” 在 MyBatis 中也代表一种占位符,该符号接受输入参数,在大括号中编写参数名称来接受对应参数。当 “#{}” 接受简单类型时可以用 value 或者其他任意名称来获取。
  • 关于 “${}” : 在 SQL 配置中,有时候需要拼接 SQL 语句(例如模糊查询时),用 “#{}” 是无法达到目的的。在 MyBatis 中,“${}” 代表一个 “拼接符号” ,可以在原有 SQL 语句上拼接新的符合 SQL 语法的语句。使用 “${}” 拼接符号拼接 SQL ,会引起 SQL 注入,所以一般不建议使用 “${}”。
  • MyBatis 使用场景: 通过上面的入门程序,不难看出在进行 MyBatis 开发时,我们的大部分精力都放在了 SQL 映射文件上。 MyBatis 的特点就是以 SQL 语句为核心的不完全的 ORM(关系型映射)框架。与 Hibernate 相比,Hibernate 的学习成本比较高,而 SQL 语句并不需要开发人员完成,只需要调用相关 API 即可。这对于开发效率是一个优势,但是缺点是没办法对 SQL 语句进行优化和修改。而 MyBatis 虽然需要开发人员自己配置 SQL 语句,MyBatis 来实现映射关系,但是这样的项目可以适应经常变化的项目需求。所以使用 MyBatis 的场景是:对 SQL 优化要求比较高,或是项目需求或业务经常变动。

参考资料:

  • 《Java EE 互联网轻量级框架整合开发》
  • 《Spring MVC + MyBatis开发从入门到项目实战》
  • How2j-MyBatis 系列教程
  • 全能的百度和万能的大脑

欢迎转载,转载请注明出处!
简书ID:@我没有三颗心脏
github:wmyskxz
欢迎关注公众微信号:wmyskxz_javaweb
分享自己的Java Web学习之路以及各种Java学习资料