MyBatis简单使用

Posted cgy-home

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis简单使用相关的知识,希望对你有一定的参考价值。

### 1. MyBatis

MyBatis是一种持久层框架。

传统的JDBC开发中,需要程序员编写大量的代码,例如创建与数据库的连接 > 获取Statement/PreparedStatement对象 > 执行SQL语句 > 获取结果 > 处理结果 > 释放资源……这个流程相对固定,代码量略多。

使用MyBatis可以极大程度的简化开发过程,程序员只需要定义需要执行的任务对应的抽象方法,并指定该方法对应的SQL语句即可。

### 2. 使用MyBatis

#### 2.1. 案例目标

通过MyBatis实现对某张用户数据表的增、删、改、查。

#### 2.2. 创建数据库与数据表

首先,创建数据库:

    CREATE DATABASE tedu_ums;

然后,使用该数据库:

    USE tedu_ums

接下来,创建数据表:

    CREATE TABLE t_user (
        id INT AUTO_INCREMENT COMMENT ‘用户id‘,
        username VARCHAR(20) UNIQUE NOT NULL COMMENT ‘用户名‘,
        password VARCHAR(20) NOT NULL COMMENT ‘密码‘,
        age INT COMMENT ‘年龄‘,
        phone VARCHAR(20) COMMENT ‘手机号码‘,
        email VARCHAR(50) COMMENT ‘电子邮箱‘,
        PRIMARY KEY(id)
    ) DEFAULT CHARSET=UTF8;

  



#### 2.3. 创建项目

创建新的`Maven Project`,`Group Id`为`cn.tedu.mybatis`,`Artifact Id`为`MyBatis`,`Packaging`选择`war`。

创建完成后,依然需要:生成`web.xml`;添加Tomcat运行环境;复制此前项目中的依赖;复制Spring的配置文件;复制`web.xml`中关于`DispatcherServlet`和`CharacterEncodingFilter`的配置。

此次需要使用MyBatis框架,所以需要添加新的依赖:

  

   <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.1</version>
    </dependency>

 



由于即将把MyBatis和Spring框架整合起来使用,所以需要添加整合2个框架的依赖:

    

 <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.1</version>
    </dependency>

 



其工作核心是依赖于JDBC的,还需要添加`spring-jdbc`依赖,该依赖的版本必须与当前项目使用的`spring-webmvc`的版本完全一致:

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>4.3.9.RELEASE</version>
    </dependency>

 


    
本次使用的是mysql数据库,还需要添加数据库连接的jar包:

   

  <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.15</version>
    </dependency>

 



另外,还需要数据库连接池的依赖:

    <dependency>
        <groupId>commons-dbcp</groupId>
        <artifactId>commons-dbcp</artifactId>
        <version>1.4</version>
    </dependency>

 

#### 2.4. 连接数据库

首先,应该在`src/main/resources`下创建`db.properties`数据库连接的配置文件:

    url=jdbc:mysql://localhost:3306/tedu_ums?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    driver=com.mysql.cj.jdbc.Driver
    username=root
    password=root
    initialSize=10
    maxActive=50

 



如果不清楚`driver`属性的值,可以展开jar包中找到配置文件来查看:

然后,在Spring的配置文件中,使用`<util:properties>`节点读取以上配置文件:

    <!-- 读取db.properties中的配置 -->
    <util:properties location="classpath:db.properties" />


读取的配置需要注入到数据库连接池的`BasicDataSource`对象中,所以,还要在Spring的配置文件中配置该类对应的`<bean>`节点,并注入属性的值,由于需要读取此前的`<util:properties>`中的值,所以,还要为该节点添加`id`:

    <!-- 读取db.properties中的配置 -->
    <util:properties id="dbConfig" location="classpath:db.properties" />
    <!-- 配置数据源 -->
    <bean id="dataSource" 
        class="org.apache.commons.dbcp.BasicDataSource">
        <property name="url" value="#dbConfig.url"/>
        <property name="driverClassName" value="#dbConfig.driver"/>
        <property name="username" value="#dbConfig.username"/>
        <property name="password" value="#dbConfig.password"/>
        <property name="initialSize" value="#dbConfig.initialSize"/>
        <property name="maxActive" value="#dbConfig.maxActive"/>
    </bean>

 


相关文件的配置关系:

然后,创建单元测试类,并测试连接是否可用:

    @Test
    public void getConnection() throws SQLException 
        ClassPathXmlApplicationContext ac
            = new ClassPathXmlApplicationContext("spring.xml");
        
        DataSource dataSource
            = ac.getBean("dataSource", DataSource.class);
        
        Connection conn =dataSource.getConnection();
        System.out.println(conn);
        
        ac.close();
    

 

#### 2.5. 接口与抽象方法

创建`cn.tedu.mybatis.mapper.UserMapper`接口文件。

创建`cn.tedu.mybatis.entity.User`实体类。

首先,完成“增加”操作,则添加抽象方法:

    // INSERT INTO t_user (username,password,age,phone,email) VALUES (?,?,?,?,?)
    Integer addnew(User user);

 

MyBatis的接口文件不必在Spring组件扫描的范围之内,即框架本身是不知道有哪些接口文件、这些文件在哪里的!所以,需要配置这些接口文件所在的位置!则在Spring的配置文件中配置`MapperScannerConfigurer`并为必要的属性注入值:

    <!-- MapperScannerConfigurer -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 持久层接口在哪个包中 -->
        <property name="basePackage" value="cn.tedu.mybatis.mapper" />
    </bean>


#### 2.6. 配置SQL语句

首先,从`doc.tedu.cn`下载**MyBatis Mapper映射文件 mybatis-mapper.zip**文件,解压得到`SomeMapper.xml`文件。

在`src\main\resources`创建名为`mappers`的文件夹,并将`SomeMapper.xml`文件复制到这个文件夹中:

![](03.png)

然后,编辑`SomeMapper.xml`文件,配置抽象方法对应的SQL语句:

  

   <!-- namespace:该XML文件对应哪个接口文件 -->
    <mapper namespace="cn.tedu.mybatis.mapper.UserMapper">
    
        <!-- 根据将要执行的SQL语句选择节点 -->
        <!-- id:抽象方法的名称 -->
        <insert id="addnew">
        INSERT INTO t_user (
            username,password,
            age,phone,
            email
        ) VALUES (
            #username,#password,
            #age,#phone,
            #email
        )
        </insert>
    
    </mapper>

 


配置的关系图为:


至此,还需要让框架知道这些XML文件在哪里,并且,找到该文件后还要执行所配置的SQL语句,就需要让框架知道如何连接数据库,则还需要配置`SqlSessionFactoryBean`:

    <!-- SqlSessionFactoryBean -->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 如何连接数据库 -->
        <property name="dataSource" ref="dataSource" />
        <!-- 配置的XML文件在哪里 -->
        <property name="mapperLocations" 
            value="classpath:mappers/*.xml" />
    </bean>


最后,可以编写并执行单元测试:

    @Test
    public void addnew() 
        ClassPathXmlApplicationContext ac
            = new ClassPathXmlApplicationContext("spring.xml");
    
        UserMapper userMapper
            = ac.getBean("userMapper", UserMapper.class);
        
        User user = new User();
        user.setUsername("FanCQ");
        user.setPassword("123456");
        Integer rows = userMapper.addnew(user);
        System.out.println("rows=" + rows);
        
        ac.close();
    

 

### 3. 其它数据访问方式

#### 3.1. 根据id删除数据

在`UserMapper.java`接口中添加新的抽象方法:

    // delete from t_user where id=?
    Integer deleteById(Integer id);

然后,在`UserMapper.xml`中配置映射:

    <delete id="deleteById">
        DELETE FROM t_user WHERE id=#id
    </delete>

 

#### 3.2. 查询指定id的用户数据

在`UserMapper.java`接口中添加新的抽象方法:

    // select * from t_user where id=?
    User findById(Integer id);

然后,在`UserMapper.xml`中配置映射:

    <select id="findById" resultType="cn.tedu.mybatis.entity.User">
        SELECT
            id,username,
            password,age,
            phone,email
        FROM
            t_user
        WHERE
            id=#id
    </select>

 


#### 3.3. 查询所有用户数据

在`UserMapper.java`接口中添加新的抽象方法:

    // select * from t_user
    List<User> findAll();

然后,在`UserMapper.xml`中配置映射:

    <select id="findAll" resultType="cn.tedu.mybatis.entity.User">
        SELECT
            id,username,
            password,age,
            phone,email
        FROM
            t_user
    </select>

 

### 4. MyBatis的设计原则

#### 4.1. 如何设计抽象方法

在MyBatis中,接口中的抽象方法的设计原则有:

1. 返回值:如果将要执行的是增、删、改操作,返回值应该是`Integer`,表示受影响的行数,如果将要执行的是查询操作,返回值可以是符合查询结果需求的数据类型;

2. 方法名称:抽象方法的名称可以自定义,但是,不允许使用重载机制,具体的命名方式推荐遵守JPA的规范;

3. 参数列表:抽象方法的参数可根据将要执行SQL语句中的变量来决定,如果执行的是`INSERT`操作,参数应该是自定义的数据类型,可以封装所有字段的值。

**注意:默认情况下,抽象方法只允许有最多1个参数!**

#### 4.2. 如何配置XML中的映射

所有的增删改查节点都必须指定`id`属性。

如果配置是的`<select>`节点,除了必须指定`id`属性以外,还必须配置`resultType`或`resultMap`中的1个。


### 5.1. 在插入数据时获取新数据的id

如果需要在插入数据时,直接获取到新数据的id(自动编号的字段的值),需要在配置XML中的映射时,添加配置`useGeneratedKeys`和`keyProperty`这2项新的属性:

   

  <!-- 根据将要执行的SQL语句选择节点 -->
    <!-- id:抽象方法的名称 -->
    <!-- useGeneratedKeys:是否需要获取自动生成的字段的值 -->
    <!-- keyProperty:获取到了自动生成的值后放到哪个属性中 -->
    <insert id="addnew"
        useGeneratedKeys="true"
        keyProperty="id">
        INSERT INTO t_user (
            username,password,
            age,phone,
            email
        ) VALUES (
            #username,#password,
            #age,#phone,
            #email
        )
    </insert>
  <!-- 根据将要执行的SQL语句选择节点 -->
    <!-- id:抽象方法的名称 -->
    <!-- useGeneratedKeys:是否需要获取自动生成的字段的值 -->
    <!-- keyProperty:获取到了自动生成的值后放到哪个属性中 -->
    <insert id="addnew"
        useGeneratedKeys="true"
        keyProperty="id">
        INSERT INTO t_user (
            username,password,
            age,phone,
            email
        ) VALUES (
            #username,#password,
            #age,#phone,
            #email
        )
    </insert>

 

当MyBatis执行时,就会获取自动编号的值,并封装到插入数据时的参数对象中,例如:

    System.out.println("插入数据之前:" + user);
    Integer rows = userMapper.addnew(user);
    System.out.println("rows=" + rows);
    System.out.println("插入数据之后:" + user);

实际运行结果为:

  插入数据之前:User [id=null, username=WangKJ, password=123456, age=18, phone=13900139001, email=[email protected]]
    rows=1
    插入数据之后:User [id=21, username=WangKJ, password=123456, age=18, phone=13900139001, [email protected]]

 

所以,当需要获取自动编号的值时,添加以上2个属性的配置,并当插入完成之后,从参数对象中获取值即可。

### 5.2. 查询时自定义别名

为`t_user`表添加新的字段`is_delete INT COMMENT "是否标记为删除,0-未删除,1-已删除"`:

    ALTER TABLE t_user ADD COLUMN is_delete INT COMMENT "是否标记为删除,0-未删除,1-已删除";

由于数据表中添加了新字段,则实体类`User`也应该进行调整:

   

  public class User 

        private Integer id;
        private String username;
        private String password;
        private Integer age;
        private String phone;
        private String email;
        private Integer isDelete;

        ... ...

 

为了保证功能正常,在插入数据对应的SQL映射中,也要添加该字段的配置:

    <insert id="addnew"
        useGeneratedKeys="true"
        keyProperty="id">
        INSERT INTO t_user (
            username, password,
            age, phone,
            email, is_delete
        ) VALUES (
            #username, #password,
            #age, #phone,
            #email, #isDelete
        )
    </insert>

 

并且,在查询时,也要查询`is_delete`字段的值,需要注意的是:MyBatis要求查询结果的列名与类的属性名一致,才可以正确的封装查询结果到对象中,如果不一致,可以取别名:

    <select id="findById" resultType="cn.tedu.mybatis.entity.User">
        SELECT
            id,username,
            password,age,
            phone,email,
            is_delete AS isDelete
        FROM
            t_user
        WHERE
            id=#id
    </select>

 

### 6. 动态SQL

动态SQL指的是在配置XML映射时,可以使用一些特定的标签实现一些逻辑,例如`<if>`标签、`<foreach>`标签等,则可以根据参数不同,生成不同的SQL语句。

案例目标:批量删除指定的一系列id的数据,例如一次性删除id为3/6/9的数据。

需要执行的SQL语句大致是:

 delete from t_user where id in (3,6,9);

 

在以上SQL语句,`(3,6,9)`可能随着用户的操作不同,而产生变化,无论是其中的值,还是值的数量,都可能不同!

所以,在设计抽象方法时:

Integer deleteByIds(List<Integer> ids);

 

然后,在配置XML映射时:

    <delete id="deleteByIds">
        DELETE FROM t_user WHERE id IN (
            <foreach collection="list"
                item="id" separator=",">
                #id
            </foreach>
        )
    </delete

以上配置中:

- `collection`:被遍历的参数,如果该参数是`List`类型的,则该属性值为`list`,如果参数是数组类型的,则该属性是`array`;

- `item`:遍历过程中的变量名,在`<foreach>`节点内部,也用该名称表示被遍历的数据;

- `separator`:分隔符;

- `open`:遍历生成的代码的最左侧的字符,例如可以是`(`,当然,前提是SQL语句中没有在动态SQL的最左侧使用`(`;

- `close`:遍历生成的代码的最右侧的字符,与`open`类似。

### 7. MyBatis中的占位符

在MyBatis中,允许使用`#`和`$`这2种格式的占位符,例如:

 <select id="find" resultType="cn.tedu.mybatis.entity.User">
        SELECT
            id,username,
            password,age,
            phone,email,
            is_delete AS isDelete
        FROM
            t_user
        <if test="where != null">
            WHERE $where
        </if>
        <if test="orderBy != null">
            ORDER BY $orderBy
        </if>
        <if test="offset != null">
            LIMIT #offset,#count
        </if>
    </select>

 

使用`#`的占位符是预编译的,只能用于在SQL语句中写值的位置,例如`delete from t_user where id=#id`,也就是在传统的JDBC编程中,可以在`?`的位置,都可以使用这种占位符,反之,不可以写`?`的位置就不可以使用这种占位符!

使用`$`的占位符不是预编译,而是在编译之前进行字符串的拼接,基本上可以取代SQL语句中的任何部分,只要保证最终能够拼接出语法正确的SQL语句即可!

所以,使用`#`的是先编译,再替换值并执行,而使用`$`的先拼接,再编译,再执行。

如果使用`$`则不是预编译的,则存在SQL注入的安全隐患。

占位符小结:如果在使用传统的JDBC编程时,能写`?`的位置,都使用`#`,否则,使用`$`。

关于动态SQL的使用,绝不提倡使用某1个方法及其映射实现许多功能!多半会导致性能下降及相关资源浪费。

### 8. MyBatis中抽象方法的参数超过1个的问题

在MyBatis的抽象方法中,默认只支持1个参数,或没有参数!

如果直接声明某个抽象方法存在多个参数,在运行时,会出现错误:
    
    Caused by: 
    org.apache.ibatis.binding.BindingException: 
    Parameter ‘password‘ not found. 
    Available parameters are [arg1, arg0, param1, param2]

可以将多个参数使用`Map`封装起来,实现“只有1个参数”,从而解决这个问题,但是,使用`Map`存在的问题:方法的调用者不明确需要向`Map`中封装几项数据,每项数据的`key`是多少等问题。

MyBatis允许在抽象方法的多个参数之前添加`@Param("名称")`,在底层实现时,会将多参数封装为`Map`,但是开发者不需要考虑这个问题,在实际编程时,只要某个抽象方法的参数超过1个,则需要为每个参数添加该注解,且在配置XML映射时,占位符中的名称必须是注解中配置的名称:

    Integer updatePassword(
            @Param("id") Integer id, 
            @Param("pwd") String password);

    <update id="updatePassword">
        UPDATE 
            t_user
        SET 
            password=#pwd
        WHERE 
            id=#id
    </update>


### 9. 多表关联查询

#### 9.1. 准备工作

创建“部门表”:

    CREATE TABLE t_department (
        id INT AUTO_INCREMENT COMMENT ‘部门id‘,
        name VARCHAR(20) NOT NULL COMMENT ‘部门名称‘,
        PRIMARY KEY (id)
    ) DEFAULT CHARSET=UTF8;

 

每个员工都应该归属某个部门,则在`t_user`表中添加新的字段,表示员工归属的部门:

 ALTER TABLE t_user ADD COLUMN department_id INT;

 

接下来,需要修改`User`类,并创建新的`Department`类。

再完成一些测试数据:

    INSERT INTO t_department (name) VALUES (‘软件研发部‘),(‘人力资源部‘),(‘财务部‘);

    UPDATE t_user SET department_id=1 WHERE id IN (1,11,12,19,20,21);
    UPDATE t_user SET department_id=2 WHERE id IN (2,10,13,16,22);
    UPDATE t_user SET department_id=3 WHERE id IN (5,7,14,15,23);

 

#### 9.2. 查询某用户信息时,直接显示该用户归属的部门的名称

需要执行的SQL语句应该是:

    SELECT 
        t_user.id,username,
        password, age, 
        phone, email, 
        is_delete,
        department_id,
        name
    FROM 
        t_user 
    LEFT JOIN
        t_department
    ON
        t_user.department_id=t_department.id
    WHERE 
        t_user.id=10;

 

由于目前没有哪个数据类型可以封装此次的查询结果,则需要创建新的数据类型,以封装查询结果。通常,这种与数据表结构并不对应,却与实际使用相对应(与查询结果相对应)的类,称之为VO类,即Value Object类,所以,需要创建`cn.tedu.mybatis.vo.UserVO`类,并在类中声明与查询结果相对应的属性:

    public class UserVO 

        private Integer id;
        private String username;
        private String password;
        private Integer age;
        private String phone;
        private String email;
        private Integer isDelete;
        private Integer departmentId;
        private String departmentName;

        ... ...

 

由于查询主体是“用户”,所以,在`UserMapper.java`中添加抽象方法:

 UserVO findUserById(Integer id);


配置的SQL映射为:

     

<select id="findUserById" resultType="cn.tedu.mybatis.vo.UserVO">
        SELECT 
            t_user.id,username,
            password, age, 
            phone, email, 
            is_delete AS isDelete,
            department_id AS departmentId,
            name AS departmentName
        FROM 
            t_user 
        LEFT JOIN
            t_department
        ON
            t_user.department_id=t_department.id
        WHERE 
            t_user.id=#id
    </select>

 

最后,编写并执行单元测试:

  @Test
    public void findUserById() 
        Integer id = 21;
        UserVO userVO = userMapper.findUserById(id);
        System.out.println(userVO);
    

#### 9.3. 查询某部门信息时,显示该部门的员工列表

需要执行的SQL语句应该是:

    SELECT 
        *
    FROM
        t_department
    LEFT JOIN
        t_user
    ON
        t_department.id=t_user.department_id
    WHERE
        t_department.id=1;

以上查询可能查询出多条结果,但是,根据本意,其实只需要1个结果!

首先,应该创建新的VO类,以封装查询结果:

    public class DepartmentVO 

        private Integer id;
        private String name;
        private List<User> users;

        ... ...

 

由于此次查询的主体是“部门”,则应该先创建`cn.tedu.mybatis.mapper.DepartmentMapper`接口,然后在该接口中添加抽象方法:

    DepartmentVO findById(Integer id);

 

然后,复制原有的`UserMapper.xml`,并粘贴回`mappers`文件夹,重命名为`DepartmentMapper.xml`,删除原有配置,然后配置映射对应以上接口与抽象方法:

    

 SELECT 
        t_department.id, name,
        t_user.id AS uid, username,
        password, age,
        phone, email,
        is_delete
    FROM
        t_department
    LEFT JOIN
        t_user
    ON
        t_department.id=t_user.department_id
    WHERE
        t_department.id=#id

 

由于查询结果应该只有1个对象,实际查询结果却有多条记录,需要自定义`resultMap`,用于指导MyBatis将多条查询结果封装到1个对象中:

    <!-- resultMap用于指导MyBatis如何将查询结果封装到对象中 -->
    <!-- id:自定义名称 -->
    <!-- type:返回的对象的类型 -->
    <resultMap id="DepartmentVO_Result"
        type="cn.tedu.mybatis.vo.DepartmentVO">
        <!-- id节点:用于配置主键字段 -->
        <!-- column:查询结果中的列名 -->
        <!-- property:返回的对象的属性名 -->
        <id column="id" property="id"/>
        <!-- result节点:用于配置非主键字段 -->
        <result column="name" property="name"/>
        <!-- collection节点:用于配置1对多的数据 -->
        <!-- property:返回的对象的属性名 -->
        <!-- ofType:集合中的元素是什么类型的 -->
        <collection property="users"
            ofType="cn.tedu.mybatis.entity.User">
            <id column="uid" property="id"/>
            <result column="username" property="username"/>
            <result column="password" property="password"/>
            <result column="age" property="age"/>
            <result column="phone" property="phone"/>
            <result column="email" property="email"/>
            <result column="is_delete" property="isDelete"/>
        </collection>
    </resultMap>

   

 

以上是关于MyBatis简单使用的主要内容,如果未能解决你的问题,请参考以下文章

MyBatis简单使用方式总结

mybatis 的简单使用

mybatis快速入门,mybatis简单实例, 如何使用mybatis

MyBatis简单使用和入门理解

Mybatis框架简单使用

SpringBoot 简单使用mybatis