Mybatis(一)

Mybatis框架学习

一、Mybatis入门
二、Dao开发方法
三、SqlMapConfig.xml 配置文件
四、Mapper.xml 映射文件


Mybatis入门

Mybatis开发dao两种方法:
一、原始dao开发方法,需要编写dao接口和dao实现类(跟ibatis一样)
二、Mybatis的mapper接口(相当于dao接口)代理开发方法。

原生JDBC程序的总结

环境

学习用的是JDK 1.7
mysql:5.146

创建实验数据库

从传智的课件里导入sql脚本生成数据库的表和数据
里面有一些用户表、订单、订单细节、价格表

jdbc程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import java.sql.*;

/**
* @author: Shelhon
* @create: 2018/9/17 14:31
* DESCRIPTION:通过单独的JDBC程序来总结其中的问题 */
public class JdbcTest {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//通过驱动管理类获取数据库链接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?charac terEncoding=utf-8", "root", "12345");
//定义sql语句 ?表示占位符
String sql = "select * from user where username = ?";
//获取预处理statement
preparedStatement = connection.prepareStatement(sql);
//设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个 参数为设置的参数值
preparedStatement.setString(1, "王五");
//向数据库发出sql执行查询,查询出结果集
resultSet = preparedStatement.executeQuery();
//遍历查询结果集
while (resultSet.next()) {
System.out.println(resultSet.getString("id") + " " + resultSet.getString("username"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放资源
if(resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
//TODO Auto-generated catch block
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}

jdbc编程步骤

1、 加载数据库驱动
2、 创建并获取数据库链接
3、 创建 jdbcstatement 对象
4、 设置 sql 语句
5、 设置 sql 语句中的参数(使用 preparedStatement)
6、 通过 statement 执行 sql 并获取结果
7、 对 sql 执行结果进行解析处理
8、 释放资源(resultSet、preparedstatement、connection)

问题总结

  1. 数据库连接,使用时才创建,不用就立即释放,对数据库进行频繁连接开启和关闭,造成数据库资源浪费,影响了数据库性能
    解决方案:使用数据库连接池来管理数据库连接
  2. 将sql语句硬编码到Java代码中,造成代码不易维护,实际应用 sql 变化的可能较大,sql 变动 需要改变 java 代码。
  3. 使用 preparedStatement 向占有位符号传参数存在硬编码,因为 sql 语句的 where 条件不 一定,可能多也可能少,修改 sql 还要修改代码,系统不易维护。
  4. 对结果集解析存在硬编码(查询列名) ,sql 变化导致解析代码变化,系统不易维护,如 果能将数据库记录封装成 pojo 对象解析比较方便。

Mybatis框架

what

MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者 只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建 statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
Mybatis通过xml或注解的方式将要执行的各种statement(statement、 preparedStatemnt、CallableStatement)配置起来,并通过java对象和statement中的sql 进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并 返回。

Mybatis 架构

1、mybatis 配置 SqlMapConfig.xml,此文件作为 mybatis 的全局配置文件,配置了 mybatis 的运行环境等信息。 mapper.xml 文件即 sql 映射文件,文件中配置了操作数据库的 sql 语句。此文件需要在 SqlMapConfig.xml 中加载。

2、 通过 mybatis 环境等配置信息构造 SqlSessionFactory 即会话工厂

3、 由会话工厂创建 sqlSession 即会话,操作数据库需要通过 sqlSession 进行。

4、mybatis 底层自定义了 Executor 执行器接口操作数据库,Executor 接口有两个实现,一个 是基本执行器、一个是缓存执行器。

5、Mapped Statement 也是 mybatis 一个底层封装对象,它包装了 mybatis 配置信息及 sql 映射信息等。mapper.xml 文件中一个 sql 对应一个 MappedStatement 对象,sql 的 id 即 是 Mappedstatement 的 id。

6、MappedStatement对sql执行输入参数进行定义,包括HashMap、基本类型、 pojo, Executor 通过 MappedStatement 在执行 sql 前将输入的 java 对象映射至 sql 中,输入参数映射就 是 jdbc 编程中对 preparedStatement 设置参数。

7、MappedStatement对sql执行输出结果进行定义,包括HashMap、基本类型、 pojo, Executor 通过 MappedStatement 在执行 sql 后将输出结果映射至 java 对象中,输出结果映射过程 相当于 jdbc 编程中对结果的解析处理过程。

入门程序

需求

根据用户 id 查询一个用户信息
根据用户名称模糊查询用户信息列表
添加用户
更新用户
删除用户

环境

用jdk1.7
idea创建Java工程,
加入 mybatis 核心包、依赖包、数据驱动包。

基本配置文件

log4j.properties:日志功能
在 classpath 下创建 log4j.properties 如下:

1
2
3
4
5
# 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 作为输出日志信息。

SqlMapConfig.xml

在 classpath 下创建 SqlMapConfig.xml,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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=u tf-8" />
<property name="username" value="root" />
<property name="password" value="mysql" />
</dataSource>
</environment>
</environments>
</configuration>

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

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

1
2
3
4
5
6
7
8
public class User {
private int id;
private String username;// 用户姓名
private String sex;// 性别
private Date birthday;// 生日
private String address;// 地址
//get/set……
}

接着就是程序编写

在 classpath 下的 sqlmap 目录下创建 sql 映射文件 Users.xml(这是属于ibatis的命名方式):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?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">
<!-- 在映射文件中配置很多sql语句 -->
<!-- 需求:通过id查询用户表的记录 -->
<!-- 通过select执行数据库查询
id:标识 映射文件中的sql 将sql语句封装到mappedStatement对象中,所以将id称为Statement的id
parameterType :指定输入参数的类型,这里指定int型
#{}表示一个占位符
#{id}:其中的id是表示接入输入的参数,参数名称就是id,如果输入参数是简单类型,#{}中的参数名可以任意可以value或者其他名称
resultType :指定sql输出结果的所映射的Java对象类型,select指定resultType表示单条记录映射的Java对象 -->
<select id="findUserById" parameterType="int" resultType="mybatis.first.User">
SELECT * FROM USER WHERE id=#{id}
</select>

<!-- 根据用户名称模糊查询用户信息,可能返回多条
${}:表示拼接sql串,将接受到的参数内容不加任何修饰拼接在sql中
使用${}拼接sql,可能会引起sql注入
${value}:接收输入参数的内容,如果传入类型是简单类型,${}中只能使用value
-->
<select id="findUserByUsername" parameterType="String" resultType="mybatis.first.User">
SELECT * FROM USER WHERE username LIKE '%${value}%'
</select>
</mapper>

namespace :命名空间,用于隔离sql语句,在使用mapper代理开发的时候,namespace有特殊重要作用。

在SqlMapConfig.xml中配置User.xml

1
2
3
<!-- 加载映射文件 --> <mappers>
<mapper resource="sqlmap/User.xml"/>
</mappers>

测试程序
MybatisFirst.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class MybatisFirst {
//会话工厂
private SqlSessionFactory sqlSessionFactory;
@Before
public void createSqlSessionFactory() throws IOException {
// 配置文件
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 使用SqlSessionFactoryBuilder从xml配置文件中创建 SqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}

// 根据 id查询用户信息
@Test
public void testFindUserById() {
// 数据库会话实例
SqlSession sqlSession = null;
try {
// 创建数据库会话实例sqlSession
sqlSession = sqlSessionFactory.openSession();
// 查询单个记录,根据用户id查询用户信息
User user = sqlSession.selectOne("test.findUserById",
10);
// 输出用户信息
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
// 根据用户名称模糊查询用户信息
@Test
public void testFindUserByUsername() {
// 数据库会话实例
SqlSession sqlSession = null;
try {
// 创建数据库会话实例sqlSession
sqlSession = sqlSessionFactory.openSession();
// 查询单个记录,根据用户id查询用户信息
List<User> list =sqlSession.selectList("test.findUserByUsername", "张");
System.out.println(list.size());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
}

添加用户

在 SqlMapConfig.xml 中添加

1
2
3
4
<!-- 添加用户 --> <insert id="insertUser" parameterType="mybatis.first.User">
insert into user(username,birthday,sex,address)
values(#{username},#{birthday},#{sex},#{address})
</insert>

测试程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test public void insertUserTest() {

SqlSession sqlSession = sqlSessionFactory.openSession();
// 插入用户对象
User user=new User();
user.setUsername("王小军");
user.setBirthday(new Date());
user.setSex("1");
user.setAddress("河南");
sqlSession.insert("test.insertUser",user);
sqlSession.commit();
sqlSession.close();
}
}

删除用户

映射文件

1
2
3
4
5
<!-- 删除用户
根据id删除用户,需要输入id值 -->
<delete id="deleteUser" parameterType="java.lang.Integer">
delete from user where id =#{id}
</delete>

测试程序

1
2
3
4
5
6
7
8
9
// 删除id=30的用户 
@Test
public void deleteUserTest() {
SqlSession sqlSession = sqlSessionFactory.openSession();
// 删除用户对象
sqlSession.insert("test.deleteUser",30);
sqlSession.commit();
sqlSession.close();
}

更新用户

映射文件

1
2
3
4
5
<!-- 更新用户
需要传入用户id和更新信息 parameterType指定User对象,包括id和更新信息,id必须存在 #{id}:从输入User对象中获取id属性值 -->
<update id="updateUser" parameterType="mybatis.first.User">
update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
</update>

测试程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 更新用户 
@Test
public void updateUserTest() {
SqlSession sqlSession = sqlSessionFactory.openSession();
//更新用户
User user=new User();
user.setId(29);
user.setUsername("王小军update");
user.setBirthday(new Date());
user.setSex("1");
user.setAddress("河南111");
sqlSession.insert("test.updateUser",user);
System.out.println(user);
sqlSession.commit();
sqlSession.close();
}

小结

一、 #{}和${}

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

$ {}表示拼接 sql 串,通过$ {}可以将 parameterType 传入的内容拼接在 sql 中且不进行 jdbc 类 型转换,${}可以接收简单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值, $ {}括号中只能是 value。

二、 parameterType 和 resultType

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

三、 selectOne 和 selectList

selectOne 查询一条记录,如果使用 selectOne 查询多条记录则抛出异常:

1
2
3
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 可以查询一条或多条记录。

四、 mysql 自增主键返回

1
2
3
4
5
6
7
8
<insert id="insertUser" parameterType="mybatis.first.User"> 
<!-- selectKey将主键返回,需要再返回 -->
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.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 值。

五、 Mysql 使用 uuid 实现主键

需要增加通过 selectuuid()得到 uuid 值
执行过程:
首先通过uuid()得到主键,将主键设置到User对象的id属性中
其次在insert执行时,从User对象中取出id属性值

1
2
3
4
5
6
7
<insert id="insertUser" parameterType="mybatis.first.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”

六、 Oracle 使用序列生成主键

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

1
2
3
4
5
6
7
<insert id="insertUser" parameterType="mybatis.first.User"> 
<selectKey resultType="java.lang.String" order="BEFORE" keyProperty="id">
SELECT 自定义序列.NEXTVAL FROM DUAL
</selectKey>
insert into user(id,username,birthday,sex,address)
values(#{id},#{username},#{birthday},#{sex},#{address})
</insert>


mybatis与hibernate的区别和应用

hibernate:是一个标准的orm框架(对象关系映射),入门门槛比较高,不需要程序写sql,sql语句自动生成了。对SQL语句进行优化、修改比较困难。
应用场景:适用于需求变化不多的中小型项目,比如:后台管理系统,erp,orm,oa等

Mybatis:专注sql本身,需要程序员自己编写SQL语句,SQL修改,优化比较方便,Mybatis是一个不完全的orm框架,虽然程序员自己写sql,但是Mybatis也可以实现映射(输入映射,输出映射)。
应用场景:适用于需求变化较多的项目,比如,互联网项目

Mybatis 和 hibernate 不同,它不完全是一个 ORM 框架,因为 MyBatis 需要程序员自己 编写 Sql 语句,不过mybatis可以通过XML 或注解方式灵活配置要运行的 sql语句, 并将 java 对象和 sql 语句映射生成最终执行的 sql,最后将 sql 执行的结果再映射生成 java 对象。

Mybatis 学习门槛低,简单易学,程序员直接编写原生态 sql,可严格控制 sql 执行性能, 灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软 件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是 mybatis 无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套 sql映射文件,工作量大。

Hibernate 对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如 需求固定的定制化软件)如果用 hibernate 开发可以节省很多代码,提高效率。但是 Hibernate 的学习门槛高,要精通门槛更高,而且怎么设计 O/R 映射,在性能和对象模型之间如何权 衡,以及怎样用好 Hibernate 需要具有很强的经验和能力才行。

总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构 都是好架构,所以框架只有适合才是最好。

Dao 开发方法

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

需求

将下边的功能实现 Dao:
根据用户 id 查询一个用户信息
根据用户名称模糊查询用户信息列表
添加用户信息

SqlSession 的使用范围

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

SqlSessionFactoryBuilder

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

SqlSessionFactory

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

SqlSession

SqlSession 是一个面向用户的接口, sqlSession 中定义了数据库操作,如:selectont(返回单个对象)、selectList(返回单个或多个对象)默认使用 DefaultSqlSession 实现类。

执行过程如下:
1、 加载数据源等配置信息 Environmentenvironment=configuration.getEnvironment();
2、 创建数据库链接
3、 创建事务对象
4、 创建 Executor,SqlSession 所有操作都是通过 Executor 完成,mybatis 源码如下:

1
2
3
4
5
6
7
8
9
10
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);
}

5、SqlSession 的实现类即 DefaultSqlSession,此对象中对操作数据库实质上用的是 Executor

结论: 每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不能共享使用,它也是 线程不安全的。因此最佳的范围是请求或方法范围。绝对不能将 SqlSession 实例的引用放在 一个类的静态字段或实例字段中。
打开一个 SqlSession;使用完毕就要关闭它。通常把这个关闭操作放到 finally 块中以 确保每次都能执行关闭。如下:

1
2
3
4
5
6
SqlSessionsession=sqlSessionFactory.openSession(); 
try{
//dowork
}finally{
session.close();
}

原始 Dao 开发方式

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

映射文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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="cn.itcast.mybatis.po.User">
select * from user where id = #{id}
</select>
<!-- 添加用户 -->
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User">
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
select LAST_INSERT_ID()
</selectKey>
insert into user(username,birthday,sex,address)
values(#{username},#{birthday},#{sex},#{address})
</insert>
</mapper>

Dao 接口

1
2
3
4
5
6
7
8
9
public interface UserDao {
//根据id查询用户信息
public User findUserById(int id) throws Exception;

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

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

接口写完就写实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Public class UserDaoImpl implements UserDao {
//注入SqlSessionFactory
public UserDaoImpl(SqlSessionFactory sqlSessionFactory){
this.setSqlSessionFactory(sqlSessionFactory);
}
private SqlSessionFactory sqlSessionFactory;
@Override
public User getUserById(int id) throws Exception {
SqlSession session = sqlSessionFactory.openSession();
User user = null;
try {
//通过sqlsession调用selectOne方法获取一条结果集
//参数1:指定定义的statement的id,参数2:指定向statement中传递的参数
user = session.selectOne("test.findUserById", 1);
System.out.println(user);
} finally{
session.close();
}
return user;
}
@Override
Public void insertUser(User user) throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
sqlSession.insert("insertUser", user);
sqlSession.commit();
} finally{
session.close();
}
}
}

测试程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class UserDaoImplTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void setUp() throws Exception{
//创建sqlSessionFactory
//mabatis配置文件
String resource ="SqlMapConfig.xml";
//得到配置文件流
InputStream inputStream= Resources.getResourceAsStream(resource);
//创建会话工厂,传入Mybatis的配置文件信息
sqlSessionFactory= new SqlSessionFactoryBuilder().build(inputStream);
}

@Test
public void findUserById() throws Exception{
//创建UserDao对象
UserDao userDao= new UserDaoImpl(sqlSessionFactory);
//调用UserDao方法
User user =userDao.findUserById(31);
System.out.println(user);
}

@Test
public void insertUser() throws Exception{
UserDao userDao =new UserDaoImpl(sqlSessionFactory);
//调用UserDao方法
User user= new User();
user.setAddress("gz");
user.setBirthday(new Date());
user.setUsername("abc1");
user.setSex("女");
userDao.insertUser(user);
}

@Test
public void deleteUser() throws Exception{
UserDao userDao =new UserDaoImpl(sqlSessionFactory);
userDao.deleteUser(31);
}
}

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

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

mapper代理方法

程序员只需要mapper接口,
还需要编写mapper.xml映射文件

程序员编写mapper接口需要遵循一些开发规范,这样Mybatis可以自动生成mapper接口实现类代理对象

Mapper 接口开发需要遵循以下规范:

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

Mapper.xml(映射文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?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="mybatis.mapper.UserMapper">
<!-- 在映射文件中配置很多sql语句 -->
<!-- 需求:通过id查询用户表的记录 -->
<select id="findUserById" parameterType="int" resultType="mybatis.first.User">
SELECT * FROM USER WHERE id=#{id}
</select>
<select id="findUserByUsername" parameterType="String" resultType="mybatis.first.User">
SELECT * FROM USER WHERE username LIKE '%${value}%'
</select>
<!-- 添加用户 -->
<insert id="insertUser" parameterType="mybatis.first.User">
<!-- selectKey将主键返回,需要再返回 -->
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
select LAST_INSERT_ID()
</selectKey>
insert into user(username,birthday,sex,address)
values(#{username},#{birthday},#{sex},#{address});
</insert>

<!-- 删除用户
根据id删除用户,需要输入id值 -->
<delete id="deleteUser" parameterType="java.lang.Integer">
delete from user where id =#{id}
</delete>

<!-- 更新用户
需要传入用户id和更新信息 parameterType指定User对象,包括id和更新信息,id必须存在 #{id}:从输入User对象中获取id属性值 -->
<update id="updateUser" parameterType="mybatis.first.User">
update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
</update>

</mapper>

Mapper.java(接口文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package mybatis.mapper;

import mybatis.first.User;

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

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

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

加载 UserMapper.xml 文件

修改 SqlMapConfig.xml 文件:

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

测试程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class UserMapperTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void setUp() throws Exception {
//创建sqlSessionFactory
//mabatis配置文件
String resource ="SqlMapConfig.xml";
//得到配置文件流
InputStream inputStream= Resources.getResourceAsStream(resource);
//创建会话工厂,传入Mybatis的配置文件信息
sqlSessionFactory= new SqlSessionFactoryBuilder().build(inputStream);
}

@Test
public void findUserById() throws Exception {
SqlSession sqlSession=sqlSessionFactory.openSession();
//创建UserMapper对象,mybatis自动生成mapper代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用UserMapper的方法
User user=userMapper.findUserById(1);
System.out.println(user);
}

@Test
public void insertUser() {
SqlSession sqlSession=sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user=new User();
user.setSex("难");
user.setUsername("nan");
user.setBirthday(new Date());
user.setAddress("Bj");
userMapper.insertUser(user);
sqlSession.commit();//少了这步会提交失败
sqlSession.close();
}

@Test
public void deleteUser() {
SqlSession sqlSession=sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.deleteUser(35);
sqlSession.commit();//少了这步会提交失败
sqlSession.close();
}
}

总结

  • selectOne 和 selectList
    动态代理对象调用sqlSession.selectOne()和sqlSession.selectList()是根据mapper接口方法的返 回值决定,如果返回 list 则调用 selectList 方法,如果返回单个对象则调用 selectOne 方法。

  • namespace
    mybatis 官方推荐使用 mapper 代理方法开发 mapper 接口,程序员不用编写 mapper 接口实 现类,使用 mapper 代理方法时,输入参数可以使用 pojo 包装对象或 map 对象,保证 dao 的通用性。

SqlMapConfig.xml

配置内容

SqlMapConfig.xml 中配置的内容和顺序如下:

  • properties(属性)
  • settings(全局配置参数)
  • typeAliases(类型别名)
  • typeHandlers(类型处理器)
  • objectFactory(对象工厂)
  • plugins(插件)
  • environments(环境集合属性对象)
  • environment(环境子属性对象)
  • transactionManager(事务管理)
  • dataSource(数据源)
  • mappers(映射器)

properties(属性)

需求:
将数据库连接参数单独配置在db.properties中,只需要在SqlMapConfig.xml中加载db.properties的属性值。在SqlMapConfig.xml中不需要对数据库连接参数硬编码。

db.properties文件

1
2
3
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8 
jdbc.username=root
jdbc.password=12345

SqlMapConfig.xml部分配置内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<configuration>
<!-- 加载属性文件 -->
<properties resource="db.properties">
<!-- properties中还可以配置一些属性名和属性值 -->
<!-- 比如<property name="jdbc.driver" value=""/> --> </properties>
<!-- 和spring整合后 environments配置将废除-->
<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 元素中 resource 或 url 加载的属性,它会覆盖已读取的同名属 性。
  • 最后读取 parameterType 传递的属性,它会覆盖已读取的同名属性。

因此,通过 parameterType 传递的属性具有最高优先级,resource 或 url 加载的属性次之, 最低优先级的是 properties 元素体内定义的属性。

建议:不要在properties元素体内体检任何属性值,只将属性值定义在properties文件中,在properties文件中定义属性名也要有一定的特殊性。

settings全局参数配置(配置)

Mybatis在运行时可以调整一些运行参数

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

比如:开启二级缓存,开启延迟加载

typeAliases(别名)

在mapper.xml中,定义很多statement,statement需要parameterType指定输入参数的类型,需要resultType指定输出结果的映射类型。

如果在指定类型时输入类型全路径,不方便开发,可以针对parameterType或resultType指定类型定义一些别名,在mapper.xml中通过别名定义,方便开发

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

自定义别名

在 SqlMapConfig.xml 中配置

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

<!-- 批量别名定义
指定包名,Mybatis自动扫描包中的po类,自动定义别名,别名就是类名(首字母大写小写都可以) -->
<package name="mybatis.first"/>

</typeAliases>

也就是说只需要写到包名里,不用写到具体的类

typeHandlers类型处理器

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

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

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

mybatis 支持类型处理器:

mappers(映射器)

Mapper 配置的几种方法:

使用相对于类路径的资源
如:

使用完全限定路径
如:

使用 mapper 接口类路径
如:

注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。
就是将mapper.java和mapper.xml放在一起且同名。

批量加载
注册指定包下的所有 mapper 接口
如:
实际测试似乎:这样也是可以加载的。
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。


Mapper.xml映射文件

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

输入映射

传递pojo对象

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

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

上面的id和username是 user 对象中的字段名称。

传递pojo的包装对象

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

  1. 定义包装对象
    定义包装对象将查询条件(pojo)以类组合的方式包装起来。
1
2
3
4
public class UserQueryVo {
private User user;
//自定义用户扩展类
private UserCustom userCustom;
  1. mapper.xml 映射文件
    在userMapper.xml中定义用户信息综合查询(查询条件复杂,通过高级查询进行)
    1
    2
    3
    4
    <!-- 用户信息综合查询 --> 
    <select id="findUserList" parameterType="mybatis.first.UserQueryVo" resultType="mybatis.first.UserCustom">
    select * from user where user.sex =#{userCustom.sex} and USER .username like '%${userCustom.username}%'
    </select>

UserMapper.java

1
2
//用户信息综合查询 
public List<UserCustom> findUserList(UserQueryVo userQueryVo) throws Exception;

测试程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test 
public void findUserList() throws Exception{
SqlSession sqlSession = sqlSessionFactory.openSession();

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

//创建包装对象,设置查询条件
UserQueryVo userQueryVo= new UserQueryVo();
UserCustom userCustom =new UserCustom();
userCustom.setSex("1");
userCustom.setUsername("张三丰");
userQueryVo.setUserCustom(userCustom);
List<UserCustom> userCustoms=userMapper.findUserList(userQueryVo);

System.out.println(userCustoms);
}

传递hashmap

Sql 映射文件定义如下:

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

其中id和username是hashmap的key

输出映射

输出简单类型

Mapper.xml 文件

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

调用

1
2
3
4
5
6
7
8
9
10
11
12
13
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 可查询单条记录。

输出 pojo 对象

Mapper.xml

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

Mapper 接口

1
public User findUserById(int id) throws Exception;

测试

1
2
3
4
5
6
7
8
9
10
11
12
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 查询单条记录。

输出 pojo 列表

参考 selectUserByName 的定义: Mapper.xml

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

Mapper 接口:

1
public List<User> findUserByUsername(String username) throws Exception;

测试

1
2
3
4
5
6
7
8
9
10
11
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 列表。

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

resultMap

Mybatis中使用resultMap完成高级输出结果映射

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

1、定义resultMap

2、使用resultMap作为statement的输出映射类型


动态sql

mybatis核心,对sql语句进行灵活操作,通过表达式判断,对sql进行灵活拼凑、组装

需求:用户信息综合查询列表和用户信息查询列表总数这两个statement的定义使用动态sql

对查询条件进行判断,如果输入参数不为空才进行查询

mapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 用户信息综合查询 --> <select id="findUserList" parameterType="mybatis.first.UserQueryVo" resultType="mybatis.first.UserCustom">
select * from user
<where>
<if test="userCustom!=null">
<if test="userCustom.sex!= null and userCustom.sex!=''">
and user.sex =#{userCustom.sex}
</if>

<if test="userCustom.username!=null and userCustom.username !=''">
and user.username like '%${userCustom.username}%'
</if>
</if>
</where>
</select>

里面用到了where和if标签
注意里面的要做不等于空字符串校验。

可以自动处理第一个 and。

Sql 片段

Sql 中可将重复的 sql 提取出来,使用时用 include 引用即可,最终达到 sql 重用的目的,如 下:

mapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 定义sql片断 id:sql片断是唯一表示 经验:是基于单表来定义sql片断,这样的话这个sql片断可重用性才高 --> 
<sql id="query_user_where">
<if test="userCustom!=null">
<if test="userCustom.sex!= null and userCustom.sex!=''">
and user.sex =#{userCustom.sex}
</if>

<if test="userCustom.username!=null and userCustom.username !=''">
and user.username like '%${userCustom.username}%'
</if>
</if>
</sql>

然后在where中使用 include 引用

1
2
3
4
5
6
7
<!-- 用户信息综合查询 --> 
<select id="findUserList" parameterType="mybatis.first.UserQueryVo" resultType="mybatis.first.UserCustom">
select * from user
<where>
<include refid="query_user_where"></include>
</where>
</select>

注意:如果引用其它 mapper.xml 的 sql 片段,则在引用时需要加上 namespace,如下: <include refid=”namespace.sql 片段”/>

foreach

在用户查询列表和查询总数的statement中增加多个输入查询
sql语句如下:
①select * from user where id =1 or id =2 or id=3

②select * from user where id IN(1,10,16)

现在要在输入参数类型中添加LISTids传入多个id

UserQueryVo.java

1
2
3
public class UserQueryVo {
//传入多个id
private List<Integer> ids;

mapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<sql id="query_user_where">
<if test="userCustom!=null">
<if test="userCustom.sex!= null and userCustom.sex!=''">
and user.sex =#{userCustom.sex}
</if>

<if test="userCustom.username!=null and userCustom.username !=''">
and user.username like '%${userCustom.username}%'
</if>
<if test="ids!=null">
<!-- 使用foreach来遍历传入ids
collection:指定输入对象中集合属性
item:每个遍历生成对象
open:开始遍历时拼接的串
close:结束遍历时拼接的串
separator:遍历的两个对象中需要拼接的串 -->
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
<!-- 每个遍历需要拼接的串 -->
id =#{id}
</foreach>
</if>
</if>
</sql>

测试程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test public void findUserList() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//创建包装对象,设置查询条件
UserQueryVo userQueryVo = new UserQueryVo();
UserCustom userCustom = new UserCustom();
//传入多个id
List<Integer> ids= new ArrayList<Integer>();
ids.add(1);
ids.add(8);
ids.add(26);
userQueryVo.setIds(ids);
userQueryVo.setUserCustom(userCustom);
List<UserCustom> userCustoms = userMapper.findUserList(userQueryVo);
System.out.println(userCustoms);
}

一开始的第二种查询的实现方式

1
2
3
4
   <foreach collection="ids" item="id" open="and id IN (" close=")" separator=",">
#{id}
</foreach>
</if>

Just for fun!
------------- 文章已经到尾 -------------