1.1mybatis下载
mybaits 的代码由github.com 管理,
地址:https://github.com/mybatis/mybatis-3/releases
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 核心包、依赖包、数据驱动包。
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>
注意这里使用的order
是BEFORE
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 编程的问题
- 数据库链接频繁建立与释放链接,造成系统资源浪费。
解决:在SqlMapConfig.xml
中配置数据链接池,使用连接池管理数据库链接。 - sql语句写在java代码中,修改的时候要修改源代码,不利于后期维护升级。
解决:将sql语句配置在XXXXmapper.xml 文件中与java 代码分离。 - 向sql 语句传参数麻烦,因为sql 语句的where 条件不一定,可能多也可能少,占位符需要和参数一一对应。
解决:Mybatis
自动将java 对象映射至sql
语句,通过statement
中的parameterType
定义输入参数的类型。 - 对结果集解析麻烦,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 实现类。
执行过程如下:
- 加载数据源等配置信息
Environment environment = configuration.getEnvironment(); - 创建数据库链接
- 创建事务对象
- 创建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);
}
- 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 接口开发需要遵循以下规范:
- Mapper.xml 文件中的namespace 与mapper 接口的类路径相同。
- Mapper 接口方法名和Mapper.xml 中定义的每个statement 的id 相同
- Mapper 接口方法的输入参数类型和mapper.xml 中定义的每个sql 的parameterType 的类型相同
- 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;
}
接口定义有如下特点:
- Mapper 接口方法名和Mapper.xml 中定义的statement 的id 相同
- Mapper 接口方法的输入参数类型和mapper.xml 中定义的statement 的parameterType 的类型相同
- 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 总结
-
selectOne
和selectList
动态代理对象调用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
元素中resource
或url
加载的属性,它会覆盖已读取的同名属性。 - 最后读取
parameterType
传递的属性,它会覆盖已读取的同名属性。 - 因此,
通过parameterType
传递的属性具有最高优先级,resource
或url
加载的属性次之,最低优先级的是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。