1. MyBatis 概述
- MyBatis 是一个半自动化的持久层框架;
- 核心SQL,开发人员可以进行优化;
- SQL和Java编码分开,功能边界清晰,一个专注业务,一个专注数据;
- JDBC:
- SQL 语句夹在Java代码块里,耦合度高,导致硬编码内伤;
- 维护不易且实际开发需求中SQL是有变化,频繁修改的情况多见;
- Hibernate 和 JPA
- 长难复杂SQL,对于Hibernate而言处理也不容易;
- 内部自动生产的SQL,不容易做特殊优化;
- 基于全映射的全自动框架,大量字段的POJO进行部分映射时,比较困难;从而导致数据库性能下降;
2. MyBatis 入门程序搭建
// 创建 Java Project
// 1. 导入 jar 包(3个)
/*
* log4j-1.2.17(用来在控制台打印 SQL 语句)
* mybatis-3.4.1
* mysql-connector-java (SQL 驱动包)
*/
// 2. 创建 Bean
// cn.itcast.mybatis.bean.Employee.java
public class Employee{
private Integer id;
private String lastName;
private String email;
// 以数字 0 表示男性, 1 表示女性
private Integer gender;
// getter 和 setter 略
}
// 3. 配置文件, 存放在 conf 文件夹下
// 创建 Source Folder, 名称为 conf
// log4j.xml
(具体见"参考资料"中链接)
// mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
<!-- 将写好的SQL映射文件,一定要注册到全局配置文件中 -->
<mappers>
<mapper resource="EmployeeMapper.xml" />
</mappers>
</configuration>
// EmployeeMapper.xml
// sql 映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.itcast.mybatis.EmployeeMapper">
<!-- namespace: 命名空间, id: 表示唯一标识, resultType: 返回值类型 -->
<select id="selectEmp" resultType="cn.itcast.mybatis.bean.Employee">
<!-- 数据库中 last_name, Bean 类中 lastName, 以下相当于用别名查询 -->
<!-- #{id} 表示从传递过来的参数中,获取id值 -->
select id,last_name lastName,email,gender from tbl_employee where id = #{id}
</select>
</mapper>
// 4. 编写测试类
public class MyBatisTest{
// 1. 根据mybatis的配置文件(全局配置文件),创建一个 SqlSessionFactory 对象;
@Test
public void test() throws IOException{
String resource = "mybatis-config.xml";
InputStream inputStream = Resorces.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream);
// 2. 获取 SqlSession 实例, 使用该实例执行数据库的增删改查;
// 一个sqlSession,就是代表和数据库的一次会话,用完需要关闭;
// sqlSession 是非线程安全的,每次使用都应该去获取新的对象;
SqlSession openSession = sqlSessionFactory.openSession();
try{
// selectOne(String statement, Object parameter) 参数说明:
// statement: sql的唯一标识, 建议写法: 命名空间+id
// parameter: 执行 sql 要用的参数
Employee employee =
openSession.selectOne("cn.itcast.mybatis.EmployeeMapper.selectEmp",1);
System.out.println(employee);
}finally{
openSession.close();
}
}
}
// 升级版本: 接口式编程
// 使用接口,实现增删改查
// 创建 cn.itcast.mybatis.dao.EmployeeMapper.java 接口
public interface EmployeeMapper{
// 按id查询
public Employee getEmpById(Integer id);
}
// 修改 EmployeeMapper.xml
// sql 映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.itcast.mybatis.dao.EmployeeMapper">
<!--
namespace: 命名空间,指定为接口的全类名;
id: 表示唯一标识,接口中的方法名;
resultType: 返回值类型
-->
<select id="getEmpById" resultType="cn.itcast.mybatis.bean.Employee">
<!-- 数据库中 last_name, Bean 类中 lastName, 以下相当于用别名查询 -->
<!-- #{id} 表示从传递过来的参数中,获取id值 -->
select id,last_name lastName,email,gender from tbl_employee where id = #{id}
</select>
</mapper>
// 修改测试类
public class MyBatisTest{
// 获取 sqlSessionFactory 对象的方法
public SqlSessionFactory getSqlSessionFactory() throws IOException{
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public test02() throws IOException{
// 1. 获取 sqlSessionFactory 对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2. 获取 SqlSession 实例
SqlSession openSession = sqlSessionFactory.openSession();
try{
// 3. 获取接口的实现类对象
// mybatis 会为接口自动创建一个代理对象, 代理对象去执行增删改查方法
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
// 调用接口实现类的方法
Employee employee = mapper.getEmpById(1);
System.out.println(employee);
}finally{
// 4. 关闭 SqlSession
openSession.close();
}
}
}
3. MyBatis 全局配置文件
3.1 常用标签介绍
<properties>
: 引入外部 properties 配置文件的内容;resource
属性: 引入类路径下的资源;url
属性: 引入网络路径或者磁盘路径下的资源;
<settings>
: 包含了很多重要的设置项<typeAliases>
: 别名处理器,可以给 java 类型起别名,方便使用;<typeHandlers>
: 类型处理器, 处理数据库类型与Java类型的转换;<plugins>
<environments>
: mybatis 可以配置多种环境, default 的值对应<environment>
中的 id 值;<databaseIdProvider>
: 设置数据库厂商;<mappers>
: 将sql映射文件注册到全局配置文件中;
// mybatis-config.xml 配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="dbconfig.properties"></properties>
<!-- 是否开启驼峰命名:即从数据库列名"a_column"到Java属性名 "aColumn" 的类似映射 -->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!-- typeAliase, 别名处理器, 别名不区分大小写 -->
<typeAliases>
<!-- 默认别名就是类名小写: employee; 也可以使用 alias 指定新的别名 -->
<typeAlias type="cn.itcast.mybatis.bean.Employee"/>
<!-- package: 为某个包下的所有类批量起别名
name: 指定包名(为当前包以及下面所有的后代包的每一个类都起一个默认别名(类名小写))
批量起别名的情况下, 使用 @Alias 注解为某个类型指定新的别名;
-->
<package name="cn.itcast.mybatis.bean"/>
</typeAliases>
<environments default="development">
<environment id="development">
<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>
<!-- 将写好的SQL映射文件,一定要注册到全局配置文件中
注册配置文件:
resource: 引入类路径下的sql映射文件;
url: 引用网络路径或者磁盘路径下的sql映射文件;
也可以注册接口:
class: 注册接口
1. 有 sql 映射文件,映射文件名必须和接口同名,并且放在与接口同一目录下;
2. 没有 sql 映射文件,所有的 sql 都是利用注解写在接口上;
备注:
比较重要,复杂的 Dao 接口,使用 sql 映射文件;
简单的Dao接口为了开发方便,可以使用注解;
-->
<mappers>
<mapper resource="EmployeeMapper.xml" />
<mapper class="cn.itcast.mybatis.bean.EmployeeMapper"/>
</mappers>
</configuration>
// dbconfig.properties 配置文件
jdbc.driver=com.mysql.jdbc.driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=root
4. MyBatis 映射文件
// 映射增删改查语句
// EmployeeMapper.java 接口
public interface EmployeeMapper{
// 单个参数
public Employee getEmpById(Integer id);
public void addEmp(Employee employee);
public void updateEmp(Employee employee);
public void deleteEmpById(Integer id);
// 备注:
// MyBatis 允许增删改直接定义以下返回值: Integer, Long, Boolean, void
// 表示增删改影响的行数, 如果行数大于0, Boolean 值为 true;
// 例如: public boolean updateEmp(Employee employee);
}
// EmployeeMapper.xml 配置文件
<mapper namespace="cn.itcast.mybatis.dao.EmployeeMapper">
<!-- 查询方法 -->
<select id="getEmpById" resultType="cn.itcast.mybatis.bean.Employee">
select * from tbl_employee where id="#{id}"
</select>
<!-- 添加方法
自增主键值的获取: MyBatis 也是利用 statement.getGeneratedKeys(); 不过,需要配置
useGeneratedKeys="true": 表示使用自增主键获取主键值策略
keyProperty: 指定对应的主键属性,也就是MyBatis获取到主键值以后,将这个值封装给 JavaBean 的哪个属性
-->
<insert id="addEmp" useGeneratedKeys="true" keyProperty="id">
insert into tbl_employee(last_name,email,gender)
values(#{lastName},#{email},#{gender})
</insert>
<!--
Oracle 不支持自增; Oracle 使用序列来模拟自增;
每次插入的数据的主键,是从序列中拿到的值,
Oracle 数据库中获取主键的配置:
-->
<insert id="addEmp" databaseId="oracle">
<!--
keyProperty: 查出的主键值封装给 JavaBean 的哪个属性
order="BEFORE": 当前sql在插入sql之前运行
resultType: 查询出数据的返回值类型
-->
<selectKey keyProperty="id" order="BEFORE" resultType="Integer">
<!-- 查询主键的sql语句 -->
select EMPLOYEES_SEQ.nextval from dual
</selectKey>
<!-- 插入数据时的主键,是从序列中拿到的 -->
insert into employees(EMPLOYEE_ID,LAST_NAME,EMAIL)
values(#{id},#{lastName},#{email})
</insert>
<!-- 更新方法 -->
<update id="updateEmp">
update tbl_employee
set last_name=#{lastName},email=#{email},gender=#{gender}
where id=#{id}
</update>
<!-- 删除方法 -->
<delete id="deleteEmpById">
delete from tbl_employee where id=#{id}
</delete>
</mapper>
// 编写测试类
public class MyBatisTest{
// 1. 获取 SqlSessionFactory 方法(同上)
// 2. 测试方法
@Test
public void test01() throws IOException{
// 1. 获取 SqlSessionFactory 对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2. 获取 SqlSession 实例
// sqlSessionFactory.openSession(); 该实例需要手动提交数据
// sqlSessionFactory.openSession(true); 该实例可以自动提交数据
SqlSession openSession = sqlSessionFactory.openSession();
try{
// 3. 获取接口的实现类对象
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
// 添加方法
Employee employee = new Employee(null,"zhangsan","zhangsan@163.com","1");
mapper.addEmp(employee);
// 输出主键值
System.out.println(employee.getId());
// 更新方法
Employee employee = new Employee(1,"lisi","lisi@163.com","1");
mapper.updateEmp(employee);
// 删除方法
mapper.deleteEmpById(2);
// 4. 手动提交
openSession.commit();
}finally{
// 5. 关闭资源
openSession.close();
}
}
}
4.1 MyBatis 映射文件的参数处理
// 示例: 查询方法中带有多个参数
// EmployeeMapper.java 接口
public interface EmployeeMapper{
// 多个参数
public Employee getEmpByIdAndLastName(Integer id, String lastName);
}
// EmployeeMapper.xml 配置文件
<mapper namespace="cn.itcast.mybatis.dao.EmployeeMapper">
<!-- 查询方法(多个参数)
出现异常: org.apache.ibatis.binding.BindingException:
Parameter 'id' not found. Available parameters are [0, 1, param1, param2]
-->
<select id="getEmpByIdAndLastName" resultType="cn.itcast.mybatis.bean.Employee">
select * from tbl_employee where id=#{id} and last_name=#{lastName}
</select>
<!-- 查询方法(多个参数)
多个参数: mybatis 会做特殊处理
多个参数会被封装成一个 map,
其中 key: param1,...paramN, 或者参数的索引
value: 传入的参数值
#{}就是从 map 中获取指定的key值
-->
<select id="getEmpByIdAndLastName" resultType="cn.itcast.mybatis.bean.Employee">
select * from tbl_employee where id=#{param1} and last_name=#{param2}
</select>
<!-- 查询方法(多个参数)
也可以使用命名参数的方法: 在封装参数时,明确指定map中的key值:
接口的写法:
public Employee getEmpByIdAndLastName(@Param("id")Integer id,
@Param("lastName")String lastName)
-->
<select id="getEmpByIdAndLastName" resultType="cn.itcast.mybatis.bean.Employee">
select * from tbl_employee where id=#{id} and last_name=#{lastName}
</select>
<!-- 查询方法(多个参数)
POJO:
如果多个参数正好是业务逻辑的数据模型,可以直接传入POJO;
#{属性名}: 取出传入的POJO类的属性值
Map:
如果多个参数不是业务模型中的数据,没有对应的POJO,不经常使用, 为了方便,也可以传入Map
#{key}: 取出map中对应的值
此时, 接口中的方法:
public Employee getEmpByMap(Map<String, Object> map);
TO:
如果多个参数不是业务模型的数据,但是经常要使用,推荐编写一个 TO(Tranfer Object)数据传输对象
特别注意: 如果是 Collection(List,Set)类型或者是数组,mybatis 也会特殊处理,把传入的list或
者数组封装到map中,
其中,key: Collection(collection), 如果是 List, 还可以使用 key(list),数组(array)
public Employee getEmpById(List<Integer> ids);
取出list集合中第一个id的值: #{list[0]}
-->
<select id="getEmpByMap" resultType="cn.itcast.mybatis.bean.Employee">
select * from tbl_employee where id = #{id} and last_name=#{lastName}
</select>
</mapper>
// 编写测试类
public class MyBatisTest{
// 1. 获取 SqlSessionFactory 方法(同上)
// 2. 测试方法
@Test
public void test01() throws IOException{
// 1. 获取 SqlSessionFactory 对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2. 获取 SqlSession 实例
// sqlSessionFactory.openSession(); 该实例需要手动提交数据
// sqlSessionFactory.openSession(true); 该实例可以自动提交数据
SqlSession openSession = sqlSessionFactory.openSession();
try{
// 3. 获取接口的实现类对象
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
// 4. 查询数据
Map<String,Object> map = new HashMap<String,Object>();
map.put("id",1);
map.put("lastName","zhangsan");
Employee employee = mapper.getEmpByMap(map);
System.out.println(employee);
}finally{
// 5. 关闭资源
openSession.close();
}
}
}
4.2 参数值的获取
#{}
: 可以获取map中的值或者pojo对象属性的值;${}
: 也可以获取map中的值或者pojo对象属性的值;- 区别:
#{}
: 是以预编译的形式,将参数设置到sql语句中, PreparedStatement: 可以防止 Sql 注入;${}
: 取出的值直接拼装在sql语句中, 会有安全问题;- 大多情况下,应该使用
#{}
;
4.3 #{}
更丰富的用法
- 参数位置支持的属性: javaType, jdbcType, mode(存储过程), numericScale, resultMap, typeHandler,
jdbcTypeName; - 实际上,通常被设置的是: 可能为空的列名指定 jdbcType;因为mybatis对所有null都映射的是原生Jdbc的 OTHER;
Oracle 数据库不能正确识别,会报错; #{email,jdbcType=NULL}
表示email列为null时,该列的类型为原生Jdbc的 NULL类型;
参考资料