mybatis学习

1.1mybatis下载

mybaits 的代码由github.com 管理,
地址:https://github.com/mybatis/mybatis-3/releases

mybatis文件包

mybatis-3.4.6.jar----mybatis 的核心包
lib----mybatis 的依赖包
mybatis-3.4.6.pdf----mybatis 使用手册

1.2创建mysql 数据库

1.3Mybatis 入门程序

1.3.1需求

实现以下功能:
根据用户id 查询一个用户信息
根据用户名称模糊查询用户信息列表
添加用户
更新用户
删除用户

1.3.2 第一步:创建java 工程

使用eclipse 创建java 工程,jdk 使用jdk1.8.0_144

1.3.3 第二步:加入jar 包

加入mybatis 核心包、依赖包、数据驱动包。

image.png

1.3.4 第三步:log4j.properties

在classpath 下创建log4j.properties如下:

# Global logging configuration
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

mybatis 默认使用log4j 作为输出日志信息。

1.3.5 第四步:SqlMapConfig.xml

在classpath 下创建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>
    <!-- 和spring整合后environments配置将废除 -->
    <environments default="development">
        <environment id="development">
            <!-- 使用jdbc事务管理 -->
            <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="mysql" />
            </dataSource>
        </environment>
    </environments>
</configuration>

SqlMapConfig.xml 是mybatis 核心配置文件,上边文件的配置内容为数据源、事务管理。

1.3.6 第五步:po 类

Po 类作为mybatis 进行sql 映射使用,po 类通常与数据库表对应,User.java 如下:

package com.ghw.po;

import java.util.Date;

public class User {

    private int id;
    private String username;// 用户姓名
    private String sex;// 性别
    private Date birthday;// 生日
    private String address;// 地址

    public int getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

}

1.3.7 第六步:程序编写

1.3.7.1查询

1.3.7.1.1 映射文件:

在classpath 下的sqlmap 目录下创建sql 映射文件Users.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="test">
</mapper>

namespace :命名空间,用于隔离sql 语句,后面会讲另一层非常重要的作用。

在Users.xml 中添加:

<mapper namespace="test">
    <!-- 根据id获取用户信息 -->
    <select id="findUserById" parameterType="int"
        resultType="com.ghw.po.User">
        select * from user where id = #{id}
    </select>
    <!-- 自定义条件查询用户列表 -->
    <select id="findUserByUsername" parameterType="java.lang.String"
        resultType="cn.itcast.mybatis.po.User">
        select * from user where username like '%${value}%'
    </select>
</mapper>

parameterType:定义输入到sql 中的映射类型,#{id}表示使用preparedstatement 设
置占位符号并将输入变量id 传到sql。
resultType:定义结果映射类型。

1.3.7.1.2 加载映射文件

mybatis 框架需要加载映射文件,将Users.xml 添加在SqlMapConfig.xml,如下:

<mappers>
        <mapper resource="sqlmap/User.xml" />
</mappers>
1.3.7.1.3 测试程序:
package com.ghw.fitst;

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

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 org.apache.log4j.Logger;
import org.junit.Test;

import com.ghw.po.User;

public class Mybatis_Test {
    static Logger logger = Logger.getLogger(Mybatis_Test.class);

    @Test
    public void findUserByIdTest() throws IOException {
        // 读取mybatis配置文件
        String resource = "SqlMapConfig.xml";
        // 得到配置文件流
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 创建会话工厂,传入mybatis配置文件流
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 通过工厂得到SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 通过SqlSession操作数据库
        // 第一个参数:映射文件中statement的id,等于=namespace+"."+statement的id
        // 第二个参数:指定和映射文件中所匹配的parameterType类型的参数
        // sqlSession.selectOne结果 是与映射文件中所匹配的resultType类型的对象
        // selectOne查询出一条记录
        User user = sqlSession.selectOne("test.findUserById", 1);
        // 输出查询到的结果
        logger.info(user);
    }
    @Test
    public void findUserByNameTest() throws IOException {
        String resource = "SqlMapConfig.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        List<User> list = sqlSession.selectList("test.findUserByusername", "小明");
        logger.info(list);
    }
}
1.3.7.1.4 #{}和${}

#{}表示一个占位符号,通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止sql注入。#{}可以接收简单类型值或pojo属性值。
如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。
${}表示拼接sql串,通过${}可以将parameterType传入的内容拼接在sql中且不进行jdbc类型转换, ${}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,${}括号中只能是value。可能引起sql注入

1.3.7.1.5 parameterType 和resultType

parameterType:指定输入参数类型,mybatis 通过ognl 从输入对象中获取参数值拼接在sql中。
resultType:指定输出结果类型,mybatis 将sql 查询结果的一行记录数据映射为resultType指定类型的对象。

1.3.7.1.6 selectOne 和selectList

selectOne 查询一条记录,如果使用selectOne 查询多条记录则抛出异常:
org.apache.ibatis.exceptions.TooManyResultsException: Expected one result(or null) to be returned by selectOne(), but found: 3
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultS
qlSession.java:70)
selectList 可以查询一条或多条记录。

1.3.7.2添加

1.3.7.2.1 映射文件:

在SqlMapConfig.xml 中添加:

<!-- 添加用户 -->
    <insert id="insertUser" parameterType="com.ghw.po.User">
        insert into user(id,username,birthday,sex,address) 
        values(#{id},#{username},#{birthday},#{sex},#{address})
</insert>

1.3.7.2.2 测试程序:

// 添加用户
    public void insertUser() {
        User user1 = new User();
        user1.setUsername("李书豪");
        user1.setBirthday(new Date());
        user1.setSex("男");
        user1.setAddress("陕西渭南");
                //在@before中已经获取了sqlSession
        sqlSession.insert("test.insertUser", user1);
        //提交事务
        sqlSession.commit();
    }

1.3.7.2.3 mysql 自增主键返回

通过修改sql 映射文件,可以将mysql 自增主键返回:

<insert id="insertUser" parameterType="com.ghw.po.User">
        <selectKey keyProperty="id" order="AFTER" resultType="Integer">
            select LAST_INSERT_ID()
        </selectKey>
        insert into user(username,birthday,sex,address) 
        values(#{username},#{birthday},#{sex},#{address})
    </insert>

添加selectKey 实现将主键返回
keyProperty:返回的主键存储在pojo中的哪个属性
order:selectKey 的执行顺序,是相对与insert 语句来说,由于mysql 的自增原理执行完insert 语句之后才将主键生成,所以这里selectKey 的执行顺序为after
resultType:返回的主键是什么类型
LAST_INSERT_ID():是mysql 的函数,返回auto_increment 自增列新记录id 值。

1.3.7.2.4 Mysql 使用uuid 实现主键

需要增加通过select uuid()得到uuid 值

<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User">
<selectKey resultType="java.lang.String" order="BEFORE"
keyProperty="id">
select uuid()
</selectKey>
insert into user(id,username,birthday,sex,address)
values(#{id},#{username},#{birthday},#{sex},#{address})
</insert>

注意这里使用的order 是“BEFORE”

1.3.7.2.5 Oracle 使用序列生成主键

首先自定义一个序列且用于生成主键,selectKey 使用如下:

<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User">
<selectKey resultType="java.lang.Integer" order="BEFORE" keyProperty="id">
SELECT 自定义序列.NEXTVAL FROM DUAL
</selectKey>
insert into user(id,username,birthday,sex,address) values(#{id},#{username},#{birthday},#{sex},#{address})
</insert>

注意这里使用的orderBEFORE

1.3.7.3删除

1.3.7.3.1 映射文件:

<!-- 删除用户 -->
    <delete id="deleteUser" parameterType="int">
        delete from user where id = #{id}
</delete>

1.3.7.3.2 测试程序:

// 删除用户
    @Test
    public void deleteUser() {
        // 删除编号32的用户
        sqlSession.delete("test.deleteUser", 32);
        // 提交事务
        sqlSession.commit();
        logger.info("删除成功");
}

1.3.7.4修改

1.3.7.4.1 映射文件

<!-- 修改用户 -->
<!-- 修改用户 -->
<update id="updateUser" parameterType="com.ghw.po.User">
    update user set
    username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} 
    where id = #{id}
</update>

1.3.7.4.2 测试程序

// 修改用户
@Test
public void updateUser() {
    User user2 = new User();
    user2.setId(26);
    user2.setBirthday(new Date());
    user2.setSex("男");
    user2.setUsername("李书豪2");
    user2.setAddress("西安邮电大学");
    sqlSession.update("test.updateUser", user2);
    sqlSession.commit();
    logger.info("修改成功");
}

1.3.8 Mybatis 解决jdbc 编程的问题

  1. 数据库链接频繁建立与释放链接,造成系统资源浪费。
    解决:在SqlMapConfig.xml中配置数据链接池,使用连接池管理数据库链接。
  2. sql语句写在java代码中,修改的时候要修改源代码,不利于后期维护升级。
    解决:将sql语句配置在XXXXmapper.xml 文件中与java 代码分离。
  3. 向sql 语句传参数麻烦,因为sql 语句的where 条件不一定,可能多也可能少,占位符需要和参数一一对应。
    解决:Mybatis自动将java 对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。
  4. 对结果集解析麻烦,sql 变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo 对象解析比较方便。
    解决:Mybatis 自动将sql 执行结果映射至java 对象,通过statement 中的resultType 定义输出结果的类型。

1.3.9 与hibernate 不同

  • Mybatis 和hibernate 不同,它不完全是一个ORM 框架,因为MyBatis 需要程序员自己编写Sql 语句,不过mybatis 可以通过XML 或注解方式灵活配置要运行的sql 语句,并将java对象和sql 语句映射生成最终执行的sql,最后将sql 执行的结果再映射生成java 对象。
  • Mybatis 学习门槛低,简单易学,程序员直接编写原生态sql,可严格控制sql 执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是mybatis 无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。
  • Hibernate 对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate 开发可以节省很多代码,提高效率。但是Hibernate的学习门槛高,要精通门槛更高,而且怎么设计O/R 映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate 需要具有很强的经验和能力才行。
  • 总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。

2 Dao 开发方法

使用Mybatis 开发Dao,通常有两个方法,即原始Dao开发方法和Mapper接口开发方法。

2.1 需求

将下边的功能实现Dao:

  • 根据用户id 查询一个用户信息
  • 根据用户名称模糊查询用户信息列表
  • 添加用户信息

2.2 SqlSession 的使用范围

SqlSession 中封装了对数据库的操作,如:查询、插入、更新、删除等。通过SqlSessionFactory 创建SqlSession,而SqlSessionFactory 是通过SqlSessionFactoryBuilder进行创建。

2.2.1 SqlSessionFactoryBuilder

SqlSessionFactoryBuilder 用于创建SqlSessionFacoty,SqlSessionFacoty 一旦创建完成就不需要SqlSessionFactoryBuilder 了,因为SqlSession 是通过SqlSessionFactory 生产,所以可以将SqlSessionFactoryBuilder 当成一个工具类使用,最佳使用范围是方法范围即方法体内局部变量。

2.2.2 SqlSessionFactory

SqlSessionFactory是一个接口,接口中定义了openSession 的不同重载方法SqlSessionFactory的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理SqlSessionFactory。

2.2.3 SqlSession

SqlSession 是一个面向用户的接口, sqlSession 中定义了数据库操作, 默认使用
DefaultSqlSession 实现类。
执行过程如下:

  1. 加载数据源等配置信息
    Environment environment = configuration.getEnvironment();
  2. 创建数据库链接
  3. 创建事务对象
  4. 创建Executor,SqlSession 所有操作都是通过Executor 完成,mybatis 源码如下:
if (ExecutorType.BATCH == executorType) {
  executor = newBatchExecutor(this, transaction);
} elseif (ExecutorType.REUSE == executorType) {
  executor = new ReuseExecutor(this, transaction);
} else {
  executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
  executor = new CachingExecutor(executor, autoCommit);
}
  1. SqlSession 的实现类即DefaultSqlSession,此对象中对操作数据库实质上用的是Executor
    结论:
    每个线程都应该有它自己的SqlSession 实例。SqlSession 的实例不能共享使用,它也是
    线程不安全的。因此最佳的范围是请求或方法范围。绝对不能将SqlSession 实例的引用放在一个类的静态字段或实例字段中。打开一个SqlSession;使用完毕就要关闭它。通常把这个关闭操作放到finally 块中以确保每次都能执行关闭。如下:
SqlSession session = sqlSessionFactory.openSession();
try {
    // do work
} finally {
    session.close();
}

2.3 原始Dao 开发方式

原始Dao 开发方法需要程序员编写Dao 接口和Dao 实现类。

2.3.1 映射文件

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

    <!-- 根据id获取用户信息 -->
    <select id="findUserById" parameterType="int"
        resultType="com.ghw.po.User">
        select * from user where id = #{id}
    </select>

    <!-- 自定义条件查询用户列表 -->
    <select id="findUserByusername" parameterType="String"
        resultType="com.ghw.po.User">
        select * from user where username like '%${value}%'
    </select>

    <!-- 添加用户 -->
    <insert id="insertUser" parameterType="com.ghw.po.User">
        <selectKey keyProperty="id" order="AFTER"
            resultType="Integer">
            select LAST_INSERT_ID()
        </selectKey>
        insert into user(username,birthday,sex,address)
        values(#{username},#{birthday},#{sex},#{address})
    </insert>

    <!-- 删除用户 -->
    <delete id="deleteUser" parameterType="int">
        delete from user where id
        = #{id}
    </delete>

    <!-- 修改用户 -->
    <update id="updateUser" parameterType="com.ghw.po.User">
        update user set
        username=#{username},birthday=#{birthday},sex=#{sex},address=#{address}
        where id = #{id}
    </update>
</mapper>

2.3.2 Dao 接口

package com.ghw.dao;

import com.ghw.po.User;

public interface UserDao {
    // 根据id查询用户
    public User getUserById(int id) throws Exception;

    // 添加用户
    public void insertUser(User user) throws Exception;

    // 删除用户
    public void deleteUser(int id) throws Exception;

    // 修改用户
    public void updateUser(User user) throws Exception;
}

Dao实现类

package com.ghw.dao;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;

import com.ghw.po.User;

public class UserDaoImp implements UserDao {
    private SqlSessionFactory sqlSessionFactory;

    public UserDaoImp(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }

    // 根据id查询用户
    public User getUserById(int id) throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        User user = sqlSession.selectOne("test.findUserById", id);
        return user;
    }

    // 添加用户
    public void insertUser(User user) throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        sqlSession.insert("test.insertUser", user);
        sqlSession.commit();
    }

    // 删除用户

    public void deleteUser(int id) throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        sqlSession.delete("test.deleteUser", id);
        sqlSession.commit();
    }

    // 修改用户
    public void updateUser(User user) throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        sqlSession.update("test.updateUser", user);
        sqlSession.commit();
    }

}

Dao测试类

package com.ghw.dao;

import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;
import com.ghw.po.User;

public class UserDaoImpTest {
    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void before() throws IOException {
        // 读取mybatis配置文件
        String resource = "SqlMapConfig.xml";
        // 得到配置文件流
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 创建会话工厂,传入mybatis配置文件流
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 通过工厂得到SqlSession
    }

    // 根据id查询用户测试方法
    @Test
    public void testGetUserById() throws Exception {
        UserDao userDao = new UserDaoImp(sqlSessionFactory);
        User user = userDao.getUserById(26);
    }

    // 添加用户测试方法
    @Test
    public void testinsertUser() throws Exception {
        UserDao userDao = new UserDaoImp(sqlSessionFactory);
        User user1 = new User();
        user1.setUsername("李书豪");
        user1.setBirthday(new Date());
        user1.setSex("男");
        user1.setAddress("陕西渭南");
        userDao.insertUser(user1);
    }

    // 删除用户测试方法
    @Test
    public void testdeleteUser() throws Exception {
        UserDao userDao = new UserDaoImp(sqlSessionFactory);
        userDao.deleteUser(29);
    }

    // 修改用户测试方法
    @Test
    public void testupdateUser() throws Exception {
        UserDao userDao = new UserDaoImp(sqlSessionFactory);
        User user2 = new User();
        user2.setId(26);
        user2.setBirthday(new Date());
        user2.setSex("女");
        user2.setUsername("李书豪1");
        user2.setAddress("西安邮电大学1");
        userDao.updateUser(user2);
    }
}

2.3.3 问题

原始Dao 开发中存在以下问题:

  • Dao 方法体存在重复代码:通过SqlSessionFactory 创建SqlSession,调用SqlSession 的数据库操作方法
  • 调用sqlSession 的数据库操作方法需要指定statement 的id,这里存在硬编码,不
    得于开发维护。

2.4 Mapper 动态代理方式

2.4.1 实现原理

Mapper 接口开发方法只需要程序员编写Mapper 接口(相当于Dao 接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao 接口实现类方法。
Mapper 接口开发需要遵循以下规范:

  1. Mapper.xml 文件中的namespace 与mapper 接口的类路径相同。
  2. Mapper 接口方法名和Mapper.xml 中定义的每个statement 的id 相同
  3. Mapper 接口方法的输入参数类型和mapper.xml 中定义的每个sql 的parameterType 的类型相同
  4. Mapper 接口方法的输出参数类型和mapper.xml 中定义的每个sql 的resultType 的类型相同

2.4.2 Mapper.xml(映射文件)

定义mapper 映射文件UserMapper.xml(内容同Users.xml),需要修改namespace 的值为UserMapper 接口路径。将UserMapper.xml 放在classpath 下mapper 目录下。

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

    <!-- 根据id获取用户信息 -->
    <select id="findUserById" parameterType="int"
        resultType="com.ghw.po.User">
        select * from user where id = #{id}
    </select>

    <!-- 自定义条件查询用户列表 -->
    <select id="findUserByusername" parameterType="String"
        resultType="com.ghw.po.User">
        select * from user where username like '%${value}%'
    </select>

    <!-- 添加用户 -->
    <insert id="insertUser" parameterType="com.ghw.po.User">
        <selectKey keyProperty="id" order="AFTER"
            resultType="Integer">
            select LAST_INSERT_ID()
        </selectKey>
        insert into user(username,birthday,sex,address)
        values(#{username},#{birthday},#{sex},#{address})
    </insert>

    <!-- 删除用户 -->
    <delete id="deleteUser" parameterType="int">
        delete from user where id
        = #{id}
    </delete>

    <!-- 修改用户 -->
    <update id="updateUser" parameterType="com.ghw.po.User">
        update user set
        username=#{username},birthday=#{birthday},sex=#{sex},address=#{address}
        where id = #{id}
    </update>
</mapper>

2.4.3 Mapper.java(接口文件)

package com.ghw.mapper;

import com.ghw.po.User;

public interface UserMapper {
    // 根据id查询用户
    public User findUserById(int id) throws Exception;

    // 自定义条件查询用户列表
    public User findUserByusername(String name) throws Exception;

    // 添加用户
    public void insertUser(User user) throws Exception;

    // 删除用户
    public void deleteUser(int id) throws Exception;

    // 修改用户
    public void updateUser(User user) throws Exception;
}

接口定义有如下特点:

  1. Mapper 接口方法名和Mapper.xml 中定义的statement 的id 相同
  2. Mapper 接口方法的输入参数类型和mapper.xml 中定义的statement 的parameterType 的类型相同
  3. Mapper 接口方法的输出参数类型和mapper.xml 中定义的statement 的resultType 的类型相同

2.4.4 加载UserMapper.xml 文件

修改SqlMapConfig.xml 文件:

<mappers>
        <!-- 加载mapper映射文件 -->
        <mapper resource="mapper/UserMapper.xml"></mapper>
    </mappers>

2.4.5 测试

package com.ghw.mapper;

import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
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 org.junit.Before;
import org.junit.Test;

import com.ghw.po.User;

public class UserMapperTest {

    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void before() throws IOException {
        // 读取mybatis配置文件
        String resource = "SqlMapConfig.xml";
        // 得到配置文件流
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 创建会话工厂,传入mybatis配置文件流
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 通过工厂得到SqlSession
    }

    // mapper根据id查询用户测试方法
    @Test
    public void testfindUserById() throws Exception {
        // 获取sqlsession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 获取mapper接口的代理对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // 调用代理对象的方法
        User user = userMapper.findUserById(26);
        // 输出查询到的内容
        System.out.println(user);
        // 关闭sqlsession
        sqlSession.close();
    }

    // mapper自定义条件查询用户列表
    @Test
    public void testfindUserByusername() throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // 通过mapper接口查询用户列表
        List<User> list = userMapper.findUserByusername("小明");
        System.out.println(list);
        // 关闭session
        sqlSession.close();
    }

    // mapper添加用户
    @Test
    public void testinsertUser() throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user1 = new User();
        user1.setUsername("李书豪");
        user1.setBirthday(new Date());
        user1.setSex("男");
        user1.setAddress("陕西渭南");
        // 通过mapper接口添加用户
        userMapper.insertUser(user1);
        // 提交
        sqlSession.commit();
        // 关闭session
        sqlSession.close();
    }

    // mapper删除用户
    @Test
    public void testdeleteUser() throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // 通过mapper接口删除
        userMapper.deleteUser(30);
        // 提交
        sqlSession.commit();
        // 关闭session
        sqlSession.close();
    }

    // mapper修改用户
    @Test
    public void testupdateUser() throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user2 = new User();
        user2.setId(28);
        user2.setBirthday(new Date());
        user2.setSex("女");
        user2.setUsername("李书豪1");
        user2.setAddress("西安邮电大学1");
        // 通过mapper接口修改用户
        userMapper.updateUser(user2);
        // 提交
        sqlSession.commit();
        // 关闭session
        sqlSession.close();
    }
}

2.4.6 总结

  • selectOneselectList
    动态代理对象调用sqlSession.selectOne()sqlSession.selectList()是根据mapper 接口方法的返回值决定,如果返回List则调用selectList()方法,如果返回单个对象则调用selectOne()方法。
  • namespace
    mybatis官方推荐使用mapper代理方法开发mapper接口,程序员不用编写mapper接口实现类,使用mapper代理方法时,输入参数可以使用pojo包装对象或map对象,保证dao的通用性。

3 SqlMapConfig.xml 配置文件

3.1 配置内容

SqlMapConfig.xml 中配置的内容和顺序如下:
properties(属性)
settings(全局配置参数)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境集合属性对象)
    environment(环境子属性对象)
        transactionManager(事务管理)
        dataSource(数据源)
mappers(映射器)

3.2 properties(属性)

SqlMapConfig.xml可以引用java属性文件中的配置信息如下:
classpath(类路径,Source Folder文件夹下就是类路径)下定义db.properties文件,内容如下:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis001?characterEncoding=utf-8
jdbc.username=root
jdbc.password=admin

SqlMapConfig.xml引用如下:

<properties resource="db.properties"/>
    <environments default="development">
        <environment id="development">
            <!-- 使用jdbc事务管理 -->
            <transactionManager type="JDBC" />
            <!-- 数据库连接池 -->
            <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>
        </environment>
    </environments>

注意: MyBatis 将按照下面的顺序来加载属性:

  • properties元素体内定义的属性首先被读取。
  • 然后会读取properties元素中resourceurl加载的属性,它会覆盖已读取的同名属性。
  • 最后读取parameterType传递的属性,它会覆盖已读取的同名属性。
  • 因此,通过parameterType传递的属性具有最高优先级,resourceurl加载的属性次之,最低优先级的是properties元素体内定义的属性。

3.3 settings(配置)

mybatis全局配置参数,全局参数将会影响mybatis的运行行为。

3.4 typeAliases(类型别名)

3.4.1 mybatis 支持别名:

别名 映射的类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal

3.4.2 自定义别名:

SqlMapConfig.xml中配置:

<typeAliases>
        <!-- 单个别名定义 -->
        <typeAlias alias="user" type="com.ghw.po.User" />
        <!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) -->
        <package name="com.ghw.po" />
        <package name="其它包" />
</typeAliases>

3.5 typeHandlers(类型处理器)

类型处理器用于java类型和jdbc类型映射,如下:

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

mybatis 自带的类型处理器基本上满足日常需求,不需要单独定义。
mybatis 支持类型处理器:

类型处理器 Java类型 JDBC类型
BooleanTypeHandler Boolean,boolean 任何兼容的布尔值
ByteTypeHandler Byte,byte 任何兼容的数字或字节类型
ShortTypeHandler Short,short 任何兼容的数字或短整型
IntegerTypeHandler Integer,int 任何兼容的数字和整型
LongTypeHandler Long,long 任何兼容的数字或长整型
FloatTypeHandler Float,float 任何兼容的数字或单精度浮点型
DoubleTypeHandler Double,double 任何兼容的数字或双精度浮点型
BigDecimalTypeHandler BigDecimal 任何兼容的数字或十进制小数类型
StringTypeHandler String CHAR和VARCHAR类型
ClobTypeHandler String CLOB和LONGVARCHAR类型
NStringTypeHandler String NVARCHAR和NCHAR类型
NClobTypeHandler String NCLOB类型
ByteArrayTypeHandler byte[] 任何兼容的字节流类型
BlobTypeHandler byte[] BLOB和LONGVARBINARY类型
DateTypeHandler Date(java.util) TIMESTAMP类型
DateOnlyTypeHandler Date(java.util) DATE类型
TimeOnlyTypeHandler Date(java.util) TIME类型
SqlTimestampTypeHandler Timestamp(java.sql) TIMESTAMP类型
SqlDateTypeHandler Date(java.sql) DATE类型
SqlTimeTypeHandler Time(java.sql) TIME类型
ObjectTypeHandler 任意 其他或未指定类型
EnumTypeHandler Enumeration类型 VARCHAR-任何兼容的字符串类型,作为代码存储(而不是索引)。

3.6 mappers(映射器)

Mapper 配置的几种方法:

3.6.1 <mapper resource=" " />

使用相对于类路径的资源
如:<mapper resource="sqlmap/User.xml" />

3.6.2 <mapper url=" " />

使用完全限定路径
如:<mapper url="file:///D:\workspace_spingmvc\mybatis_01\config\sqlmap\User.xml" />

3.6.3 <mapper class=" " />

使用mapper 接口类路径
如:<mapper class="cn.itcast.mybatis.mapper.UserMapper"/>
注意:此种方法要求mapper 接口名称和mapper 映射文件名称相同,且放在同一个目录中。

3.6.4 <package name=""/>

注册指定包下的所有mapper 接口
如:<package name="cn.itcast.mybatis.mapper"/>
注意:此种方法要求mapper 接口名称和mapper 映射文件名称相同,且放在同一个目录中。

4 Mapper.xml 映射文件

Mapper.xml 映射文件中定义了操作数据库的sql,每个sql 是一个statement,映射文件是mybatis 的核心。

4.1 parameterType(输入类型)

4.1.1 #{}与${}

#{}实现的是向prepareStatement 中的预处理语句中设置参数值,sql 语句中#{}表示一个占位
符即?。

<!-- 根据id获取用户信息 -->
<select id="findUserById" parameterType="int"
    resultType="com.ghw.po.User">
    select * from user where id = #{id}
</select>

使用占位符#{}可以有效防止sql 注入,在使用时不需要关心参数值的类型,mybatis 会自动
进行java 类型和jdbc 类型的转换。#{}可以接收简单类型值或pojo 属性值,如果parameterType
传输单个简单类型值,#{}括号中可以是value 或其它名称。
${}和#{}不同,通过${}可以将parameterType 传入的内容拼接在sql 中且不进行jdbc 类型转
换, ${}可以接收简单类型值或pojo 属性值,如果parameterType 传输单个简单类型值,${}
括号中只能是value。使用${}不能防止sql 注入,但是有时用${}会非常方便,如下的例子:

<!-- 自定义条件查询用户列表 -->
<select id="findUserByusername" parameterType="String"
    resultType="com.ghw.po.User">
    select * from user where username like '%${value}%'
</select>

如果本例子使用#{}则传入的字符串中必须有%号,而%是人为拼接在参数中,显然有点麻烦,如果采用${}在sql 中拼接为%的方式则在调用mapper 接口传递参数就方便很多。

// 如果使用占位符号则必须人为在传参数中加%
List<User> list = userMapper.selectUserByName("%管理员%");
// 如果使用${}原始符号则不用人为在参数中加%
List<User> list = userMapper.selectUserByName("管理员");

再比如order by 排序,如果将列名通过参数传入sql,根据传的列名进行排序,应该写为:
ORDER BY ${columnName}
如果使用#{}将无法实现此功能。

4.1.2 传递简单类型

参考上边的例子。

4.1.3 传递pojo 对象

Mybatis 使用ognl 表达式解析对象字段的值,如下例子:

<!-- 传递pojo对象综合查询用户信息 -->
<select id="findUserByUser" parameterType="user"
    resultType="user">
    select * from user where id=#{id} and username like '%${username}%'
</select>

上边红色标注的是user 对象中的字段名称。
测试:

public void testFindUserByUser() throws Exception {
    // 获取session
    SqlSession session = sqlSessionFactory.openSession();
    // 获限mapper接口实例
    UserMapper userMapper = session.getMapper(UserMapper.class);
    // 构造查询条件user对象
    User user = new User();
    user.setId(1);
    user.setUsername("管理员");
    // 传递user对象查询用户列表
    List<User> list = userMapper.findUserByUser(user);
    // 关闭session
    session.close();
}

异常测试:
Sql中字段名输入错误后测试,username 输入dusername 测试结果报错:
org.apache.ibatis.exceptions.PersistenceException:

### Error querying database.  Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'dusername' in 'where clause'
### The error may exist in mapper/UserMapper.xml
### The error may involve com.ghw.mapper.UserMapper.findUserByusername-Inline
### The error occurred while setting parameters

4.1.4 传递pojo 包装对象

开发中通过pojo 传递查询条件,查询条件是综合的查询条件,不仅包括用户查询条件
还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。

4.1.4.1定义包装对象

定义包装对象将查询条件(pojo)以类组合的方式包装起来。

public class QueryVo {
    private User user;
    //自定义用户扩展类
    private UserCustom userCustom;

4.1.4.2mapper.xml 映射文件

<!-- 查询用户列表 根据用户名称和用户性别查询用户列表 -->
<select id="findUserList" parameterType="queryVo"
    resultType="user">
    select * from user where username = #{user.username} and sex = #{user.sex}
</select>

说明:mybatis 底层通过ognl 从pojo 中获取属性值:#{user.username},user 即是传入的包装对象的属性。queryVo 是别名,即上边定义的包装对象类型。

说明:mybatis 底层通过 ognl 从 pojo 中获取属性值:#{user.username},user 即是传入的包 装对象的属性。queryVo 是别名,即上边定义的包装对象类型。

4.1.5 传递hashmap

Sql 映射文件定义如下:

<!-- 传递hashmap综合查询用户信息 -->
<select id="findUserByHashmap" parameterType="hashmap"
    resultType="user"> select * from user where id=#{id} and username like
    '%${username}%'
</select>

上面的id和username是hashmap的key

测试:

public void testFindUserByHashmap() throws Exception {
    // 获取session
    SqlSession session = sqlSessionFactory.openSession();
    // 获限mapper接口实例
    UserMapper userMapper = session.getMapper(UserMapper.class);
    // 构造查询条件Hashmap对象
    HashMap<String, Object> map = new HashMap<String, Object>();
    map.put("id", 1);
    map.put("username", "管理员");
    // 传递Hashmap对象查询用户列表 List<User>list = userMapper.findUserByHashmap(map);
    // //关闭session session.close();
}

异常测试: 传递的 map 中的 key 和 sql 中解析的 key 不一致。 测试结果没有报错,只是通过 key 获取值为空。

4.2 resultType(输出类型)

4.2.1输出简单类型

参考 getnow 输出日期类型,看下边的例子输出整型:
Mapper.xml 文件

<!-- 获取用户列表总数 -->
<select id="findUserCount" parameterType="user" resultType="int">
    select count(1) from user
</select>

Mapper 接口
public int findUserCount(User user) throws Exception;
调用:

public void testFindUserCount() throws Exception {
    // 获取session
    SqlSession session = sqlSessionFactory.openSession();
    // 获取mapper接口实例
    UserMapper userMapper = session.getMapper(UserMapper.class);
    User user = new User();
    user.setUsername("管理员");
    // 传递Hashmap对象查询用户列表
    int count = userMapper.findUserCount(user);
    // 关闭session session.close();
}

总结: 输出简单类型必须查询出来的结果集有一条记录,最终将第一个字段的值转换为输出类型。 使用 session 的 selectOne 可查询单条记录。

4.2.2输出 pojo 对象

参考 findUserById 的定义:
Mapper.xml

<!-- 根据id查询用户信息 -->
<select id="findUserById" parameterType="int" resultType="user"> select
    * from user where id = #{id}
</select>

Mapper 接口:
public User findUserById(int id) throws Exception;
测试:

public void testFindUserById() throws Exception {
    // 获取session
    SqlSession session = sqlSessionFactory.openSession();
    // 获限mapper接口实例
    UserMapper userMapper = session.getMapper(UserMapper.class);
    // 通过mapper接口调用statement
    User user = userMapper.findUserById(1);
    System.out.println(user);
    // 关闭session
    session.close();
}

使用 session 调用 selectOne 查询单条记录。

4.2.3输出 pojo 列表

参考 selectUserByName 的定义:
Mapper.xml

<!-- 根据名称模糊查询用户信息 -->
<select id="findUserByUsername" parameterType="string"
    resultType="user"> select * from user where username like '%${value}%'
</select>

Mapper 接口:
public List<User> findUserByUsername(String username) throws Exception;
测试:

public void testFindUserByUsername() throws Exception {
    // 获取session
    SqlSession session = sqlSessionFactory.openSession();
    // 获限mapper接口实例
    UserMapper userMapper = session.getMapper(UserMapper.class);
    // 如果使用占位符号则必须人为在传参数中加%
    // List<User> list = userMapper.selectUserByName("%管理员%");
    // 如果使用${}原始符号则不用人为在参数中加%
    List<User> list = userMapper.findUserByUsername("管理员");
    // 关闭session
    session.close();
}

使用 session 的 selectList 方法获取 pojo 列表。

4.2.4 resultType 总结:

输出 pojo 对象和输出 pojo 列表在 sql 中定义的 resultType 是一样的。 返回单个pojo 对象要保证 sql 查询出来的结果集为单条,内部使用 session.selectOne 方法调用,mapper 接口使用 pojo 对象作为方法返回值。
返回 pojo 列表表示查询出来的结果集可能为多条,内部使用 session.selectList 方法,mapper 接口使用 List<pojo>对象作为方法返回值。

4.2.5 输出 hashmap

输出 pojo 对象可以改用 hashmap 输出类型,将输出的字段名称作为 map 的 key,value 为字 段值。

4.3 resultMap

resultType 可以指定 pojo 将查询结果映射为 pojo,但需要 pojo 的属性名和 sql 查询的列 名一致方可映射成功。 如果 sql 查询字段名和 pojo 的属性名不一致,可以通过 resultMap 将字段名和属性名作 一个对应关系 ,resultMap 实质上还需要将查询结果映射到 pojo 对象中。 resultMap 可以实现将查询结果映射为复杂类型的 pojo,比如在查询结果映射对象中包 括 pojo 和 list 实现一对一查询和一对多查询。

4.3.1 Mapper.xml 定义

<!-- 查询用户列表 根据用户名称和用户性别查询用户列表 -->
<select id="findUserListResultMap" parameterType="queryVo"
    resultMap="userListResultMap">
    select id id_,username username_,birthday birthday_ from user
    <!-- where自动将第一个and去掉 -->
    <where>
        <!-- refid:指定sql片段的id,如果要引用其他命名空间的sql片段,需要前边加namaspcae -->
        <include refid="query_user_where"></include>
    </where>
</select>

使用 resultMap 指定上边定义的 personmap。

4.3.2定义 resultMap

由于上边的 mapper.xml 中 sql 查询列和 Users.java 类属性不一致,需要定义 resultMap: userListResultMap 将 sql 查询列和 Users.java 类属性对应起来

<!-- 定义resultMap,将用户查询的字段和user这个pojo的属性作一个对应关系 -->
<!-- type:最终映射的java对象 id:resultMap的唯一标识 -->
<resultMap type="user" id="userListResultMap">
    <!-- id标签:将查询结果集的唯一标识列(主键或唯一标识) 
    column:sql查询字段名(列名) 
    property:pojo属性名 -->
    <id column="id_" property="id" />
    <result column="username_" property="username" />
    <result column="birthday_" property="birthday" />
</resultMap>

<id/>:此属性表示查询结果集的唯一标识,非常重要。如果是多个字段为复合唯一约束则 定义多个<id/>。
Property:表示 person 类的属性。
Column:表示 sql 查询出来的字段名。
Column 和 property 放在一块儿表示将 sql 查询出来的字段映射到指定的 pojo 类属性上。
<result/>:普通结果,即 pojo 的属性

4.3.3 Mapper 接口定义

public List<User> findUserListResultMap() throws Exception;

4.4 动态 sql(重点)

通过 mybatis 提供的各种标签方法实现动态拼接 sql。

4.4.1If

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容