一、myBatis核心概念
知识点
- 基本概念
- 核心对象的作用域与生命周期
- 接口式编程
1、基本概念
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis可以使用简单的XML或注解来配置和映射原生信息,将接口和Java的POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。
其它持久层解决方案:JDBC、DBUtils、JdbcTemplate、Hibernate
2、核心对象的作用域与生命周期
1)新建数据库表:user
CREATE TABLE `user` (
`id` tinyint(4) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
复制代码
2)新建pojo实体类:User
public class User implements Serializable {
private static final long serialVersionUID = -6611101295813775324L;
private Integer id;
private String name;
private Date createTime;
private Long updateTime;
//省略setter、getter方法
}
复制代码
3)配置数据库连接属性:jdbc.properties
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
jdbc.username=root
jdbc.password=root
default.environment=dev
复制代码
4)配置mybatis配置文件: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="jdbc.properties"></properties>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<environments default="${default.environment}">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers
<mapper resource="mapper/UserMapper.xml"></mapper>
</mappers>
</configuration>
复制代码
5)自定义类型处理器:CustomHandler
@MappedJdbcTypes(JdbcType.TIMESTAMP)
@MappedTypes(Long.class)
public class CustomHandler extends BaseTypeHandler<Long> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Long parameter, JdbcType jdbcType) throws SQLException {
//ps.setDate(i,new Date(parameter));
ps.setTimestamp(i,new Timestamp(parameter));
}
@Override
public Long getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getDate(columnName).getTime();
}
@Override
public Long getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getDate(columnIndex).getTime();
}
@Override
public Long getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getDate(columnIndex).getTime();
}
}
复制代码
6)配置mapper.xml文件:UserMapper.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="com.cyan.mapper.UserMapper">
<resultMap id="baseResultMap" type="com.cyan.pojo.User">
<id property="id" column="id" jdbcType="INTEGER"></id>
<result property="name" column="name" jdbcType="VARCHAR"></result>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"></result>
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP" typeHandler="com.cyan.handler.CustomHandler"></result>
</resultMap>
<select id="selectUserById" parameterType="java.lang.Integer" resultMap="baseResultMap">
select * from user where id = #{id}
</select>
</mapper>
复制代码
7)编写测试用例:UserTest
public class UserTest {
private SqlSession session;
@Before
public void init()throws IOException{
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
session = sqlSessionFactory.openSession(true);
}
@Test
public void test01() throws IOException {
User result = session.selectOne("com.cyan.mapper.UserMapper.selectUserById", 1);
System.out.println(result.toString());
}
}
复制代码
SqlSessionFactoryBuilder:用于构建会话工厂,基于mybatis-config.xml中的environment 、props 构建会话工厂,构建完成后即可丢弃。
SqlSessionFactory:用于生成会话的工厂,作用于整个应用运行期间,一般不需要构造多个工厂对象
SqlSession:作用于单次会话,如WEB一次请求期间,不能用作于某个对像属性,也不能在多个线程间共享,因为它是线程不安全的。
3、接口式编程
由于每次调用时都去找对应用statement以及拼装参数,使用上不是特别友好
myBatis引入了接口的机制,将接口与mapper.xml 的namespace名称绑定,MyBatis就可以根据ASM工具动态构建该接口的实例。
1)创建mapper接口:UserMapper
User selectUser(Integer id);
复制代码
2)配置mapper.xml文件:UserMapper.xml
<select id="selectUser" resultType="com.tuling.ssm.pojo.User">
select * from user where id = #{id}
</select>
复制代码
3)编写测试用例:UserTest
UserMapper $proxy = session.getMapper(UserMapper.class);
User user = $proxy.selectUserById(2);
System.out.println(user.toString());
复制代码
二、全局的 configuration 配置
知识点
- properties属性
- environments属性
- settings属性
- typeAliases属性
- typeHandlers属性
- mappers映射器
1、properties属性
properties元素可以通过resource或url加载外部properties文件中的属性,也可以直接设置property属性。然后在xml中就可以通过${属性名}进行引用替换。
<properties resource="jdbc.properties"></properties>
复制代码
引用属性方式
${jdbc.driverClassName}
# MyBatis3.4.2开始,支持指定默认值
${jdbc.driverClassName:com.mysql.jdbc.Driver}
复制代码
2、environments属性
一个项目经常需要在例如开发坏境、测试环境、预上线环境、生产环境中等不同环境中进行部署,每个环境所对应的参数是不一样的,myBatis 中可以通过 environment 来设置不同环境的属性。
# 指定应用环境
default.environment=dev
复制代码
<environments default="${default.environment}">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
复制代码
3、settings属性
设置MyBatis 全局参数,约定myBatis的全局行为
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
复制代码
4、typeAliases属性
在myBatis中经常会用到java中类型,如sql块中的参数集中javaType、结果集映射中的javaType等 ,都要使用java 全路径名,可以通过typeAliases属性设置别名
<typeAliases>
<!--<typeAlias type="com.cyan.pojo.User" alias="User"></typeAlias>-->
<package name="com.cyan.pojo"></package>
</typeAliases>
复制代码
5、typeHandlers属性
持久层框架其中比较重要的工作就是处理数据的映射转换,把java类型转换成jdbc类型的参数,又需要把jdbc类型的结果集转换成java类型。在mybatis中是通过TypeHandler接口来实现的。
自定义类型处理器
public class CustomHandler extends BaseTypeHandler<Long> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Long parameter, JdbcType jdbcType) throws SQLException {
//ps.setDate(i,new Date(parameter));
ps.setTimestamp(i,new Timestamp(parameter));
}
@Override
public Long getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getDate(columnName).getTime();
}
@Override
public Long getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getDate(columnIndex).getTime();
}
@Override
public Long getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getDate(columnIndex).getTime();
}
}
复制代码
配置文件方式应用
<typeHandlers>
<typeHandler handler="com.cyan.handler.CustomHandler"
javaType="long" jdbcType="TIMESTAMP" />
</typeHandlers>
复制代码
注解方式应用
@MappedJdbcTypes(JdbcType.TIMESTAMP)
@MappedTypes(Long.class)
public class CustomHandler extends BaseTypeHandler<Long> {}
复制代码
6、mappers映射器
<mappers>
<mapper resource="mapper/UserMapper.xml"></mapper>
<mapper resource="mapper/AccountMapper.xml"></mapper>
<!-- mapper与对应的xml文件必须在一个包下 -->
<!--<mapper class="com.cyan.mapper.UserMapper"/>-->
<!--<package name="com.cyan.mapper"/>-->
</mappers>
复制代码
resource:基于classpath加载xml文件
class:基于接口加载
package:扫描包下所有class,然后进行加载
约定规则:
1)mapper中的namespace必须与对应的接口名称对应
2)通过class或package加载时,xml文件必须与接口在同一级目录
三、mapper映射文件
知识点
- sql语句块statement
- 参数映射
- 结果集映射
1. sql语句块statement
1)Mapper中的常见元素
resultMap – 结果集映射
select – 查询语句
insert – 插入语句
cache – 对给定命名空间的缓存配置
parameterMap - 参数集映射
update – 更新语句
delete – 删除语句
cache-ref - 指定缓存命名空间
sql – 可被其他语句引用的可重用语句块。
复制代码
2)select中的常见属性
id - 语句块的唯一标识,与接口中方法名称对应
parameterMap - 参数集映射
parameterType - 参数java类型
resultMap - 返回结果映射
resultType - 返回结果java类型
statementType - 预处理类型
timeout - 超时时间
flushCache - 每次调用都会刷新一二级缓存
useCache - 是否保存至二级缓存当中去
复制代码
3)insert&update&delete中的常见属性
id - 语句块的唯一标识,与接口中方法名称对应
parameterMap - 参数集映射
parameterType - 参数java类型
statementType - 预处理类型
timeout - 超时时间
flushCache- true每次调用都会刷新一二级缓存
# insert、update还具有如下三个属性(delete则没有)
keyProperty - 主键对应的java属性,多个用 逗号分割
keyColumn - 主键列,多个用逗号分割
useGeneratedKeys - 插入成功后可以获取到数据库自动生成的主键值
复制代码
2、参数映射
参数映射是最强大功能之一,基本可以通过以下方式进行引用
1)单个简单参数引用:如果方法中只有一个参数可通过任意名称进行引用
User selectUserById(Integer id);
<select id="selectUserById" parameterType="java.lang.Integer" resultMap="baseResultMap">
select * from user where id = #{id}
</select>
复制代码
2)多个简单参数引用:通过参数下标引用#{param1},#{param2}
Integer insertUser(String name, Date createTime, Date updateTime);
<insert id="insertUser">
insert into user(name,create_time,update_time) values (#{param1},#{param2},#{param3})
</insert>
复制代码
3)对象属性引用:直接通过对象属性名称引用,嵌套对象通过.进行引用
Integer saveUser(User user);
<insert id="saveUser" parameterType="com.cyan.pojo.User"
keyColumn="id" keyProperty="id" useGeneratedKeys="true">
insert into user(name,create_time,update_time) values (#{name},#{createTime},#{updateTime,typeHandler=com.cyan.handler.CustomHandler})
</insert>
复制代码
4)map key值引用
<update id="updateUserById" parameterType="java.util.Map">
update user set name = #{name},update_time = #{updateTime} where id = #{id}
</update>
复制代码
5)变量名引用
Integer modifyUserById(@Param("id") Integer id,@Param("name") String name,@Param("updateTime")Date updateTime);
<update id="modifyUserById">
update user set name = #{name},update_time = #{updateTime} where id = #{id}
</update>
复制代码
6)参数拼接
基于#的参数引用,其原理是通过?占位符进行预处理能获得更好的性能和安全性(防止SQL注入),但有些需求是通过?占位无法实现的,比如在一些分库分表的场景中我们需要动态的拼接表结构。比如某系统日志表是按年进行切割的2018_systemlog,2019_systemlog这时就可以通过如下语句进行
@Select("select * from ${year}_user where id = #{id}")
User selectUserTableByYear(String year, Integer id);
复制代码
3、结果集映射
1)结果集自动映射
在select中指定resultType=""后无需要任何配置 myBatis会基于resultType中的java类型及属性自动推断生成一个隐示的resultMap,从而完成结果映射
复制代码
2)resultMap
有时jdbc并不是与java Bean完全贴合这时就需要手动设置resultMap
<resultMap id="baseResultMap" type="com.cyan.pojo.User">
<id property="id" column="id" jdbcType="INTEGER"></id>
<result property="name" column="name" jdbcType="VARCHAR"></result>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"></result>
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP" typeHandler="com.cyan.handler.CustomHandler"></result>
</resultMap>
<select id="selectUserById" parameterType="java.lang.Integer" resultMap="baseResultMap">
select * from user where id = #{id}
</select>
ID:用于结果集中的唯一标识
result:设置一个某通过字段
property:java属性名
jdbcType:jdbc类型
javaType:java类型
column:数据库列名
typeHandler:类型处理器
复制代码
3)嵌套结果映射(一对一)
创建相关数据库:
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`money` int(11) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
复制代码
AccountMapper.xml:
<resultMap id="accountAndUserResultMap" type="com.cyan.pojo.Account">
<id property="id" column="id"/>
<association property="user" javaType="com.cyan.pojo.User">
<id property="id" column="user_id"/>
<result property="name" column="userName"/>
</association>
</resultMap>
<select id="getAccountList" resultMap="accountAndUserResultMap">
SELECT a.*, u.name userName from account a,user u where a.user_id=u.id
</select>
复制代码
4)引入外部select(一对一)
AccountMapper.xml:
<resultMap id="accountAmpUserResultMap" type="com.cyan.pojo.Account">
<id property="id" column="id"/>
<association property="user" javaType="com.cyan.pojo.User"
select="com.cyan.mapper.UserMapper.selectUserById" column="user_id">
</association>
</resultMap>
<select id="selectAccountList" resultMap="accountAmpUserResultMap">
SELECT a.*, u.name userName from account a,user u where a.user_id=u.id
</select>
复制代码
5)嵌套结果映射(1对多)
创建相关数据库:
CREATE TABLE teacher(
t_id INT PRIMARY KEY AUTO_INCREMENT,
t_name VARCHAR(20)
);
INSERT INTO teacher(t_name) VALUES('LS1');
INSERT INTO teacher(t_name) VALUES('LS2');
CREATE TABLE class(
c_id INT PRIMARY KEY AUTO_INCREMENT,
c_name VARCHAR(20),
teacher_id INT
);
ALTER TABLE class ADD CONSTRAINT fk_teacher_id FOREIGN KEY (teacher_id) REFERENCES teacher(t_id);
INSERT INTO class(c_name, teacher_id) VALUES('bj_a', 1);
INSERT INTO class(c_name, teacher_id) VALUES('bj_b', 2);
CREATE TABLE student(
s_id INT PRIMARY KEY AUTO_INCREMENT,
s_name VARCHAR(20),
class_id INT
);
INSERT INTO student(s_name, class_id) VALUES('xs_A', 1);
INSERT INTO student(s_name, class_id) VALUES('xs_B', 1);
INSERT INTO student(s_name, class_id) VALUES('xs_C', 1);
INSERT INTO student(s_name, class_id) VALUES('xs_D', 2);
INSERT INTO student(s_name, class_id) VALUES('xs_E', 2);
INSERT INTO student(s_name, class_id) VALUES('xs_F', 2);
复制代码
ClassesMapper.xml:
<!-- 根据 classId 查询对应的班级信息,包括学生,老师 -->
<!-- 方式一:嵌套结果(使用嵌套结果映射来处理重复的联合结果的子集) -->
<resultMap id="baseResultMap1" type="com.cyan.pojo.Classes">
<id property="id" column="c_id" />
<id property="name" column="c_name" />
<association property="teacher" column="teacher_id" javaType="com.cyan.pojo.Teacher">
<id property="id" column="t_id" />
<result property="name" column="t_name" />
</association>
<collection property="students" column="c_id" ofType="com.cyan.pojo.Student">
<id property="id" column="s_id" />
<result property="name" column="s_name" />
</collection>
</resultMap>
<select id="getClassesById" parameterType="int" resultMap="baseResultMap1">
select * from class c,teacher t, student s
where c.teacher_id = t.t_id
and c.c_id = s.class_id
and c.c_id = #{id}
</select>
复制代码
6)引入外部select(一对多)
ClassesMapper.xml:
<!-- 根据 classId 查询对应的班级信息,包括学生,老师 -->
<!-- 方式二:嵌套查询(通过执行另外一个SQL映射语句来返回预期的复杂类型) -->
<resultMap id="baseResultMap2" type="com.cyan.pojo.Classes">
<id property="id" column="c_id" />
<id property="name" column="c_name" />
<association property="teacher" column="teacher_id" javaType="com.cyan.pojo.Teacher"
select="com.cyan.mapper.TeacherMapper.getTeacherById" />
<collection property="students" column="c_id" ofType="com.cyan.pojo.Student"
select="com.cyan.mapper.StudentMapper.getStudentById"/>
</resultMap>
<select id="findClassesById" parameterType="int" resultMap="baseResultMap2">
select * from class where c_id = #{id}
</select>
复制代码
TeacherMapper.xml:
<resultMap id="baseResultMap" type="com.cyan.pojo.Teacher">
<id property="id" column="t_id" />
<result property="name" column="t_name" />
</resultMap>
<select id="getTeacherById" resultMap="baseResultMap" parameterType="int">
select * from teacher where t_id = #{id}
</select>
复制代码
StudentMapper.xml:
<resultMap id="baseResultMap" type="com.cyan.pojo.Student">
<id property="id" column="s_id" />
<result property="name" column="s_name" />
</resultMap>
<select id="getStudentById" resultMap="baseResultMap" parameterType="int">
select * from student where class_id = #{id}
</select>
复制代码