一.简介
1.1.什么是 MyBatis?
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
1.2.基本信息
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。
1.3.特点
-
简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
-
灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
-
解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
-
提供映射标签,支持对象与数据库的orm字段关系映射
-
提供对象关系映射标签,支持对象关系组建维护
-
提供xml标签,支持编写动态sql。
1.4.总体流程
(1)加载配置并初始化
触发条件:加载配置文件
处理过程:将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。
(2)接收调用请求
触发条件:调用Mybatis提供的API
传入参数:为SQL的ID和传入参数对象
处理过程:将请求传递给下层的请求处理层进行处理。
(3)处理操作请求
触发条件:API接口层传递请求过来
传入参数:为SQL的ID和传入参数对象
处理过程:
(A)根据SQL的ID查找对应的MappedStatement对象。
(B)根据传入参数对象解析MappedStatement对象,得到最终要执行的SQL和执行传入参数。
(C)获取数据库连接,根据得到的最终SQL语句和执行传入参数到数据库执行,并得到执行结果。
(D)根据MappedStatement对象中的结果映射配置对得到的执行结果进行转换处理,并得到最终的处理结果。
(E)释放连接资源。
(4)返回处理结果将最终的处理结果返回。
1.5.功能架构
(1)API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
(2)数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
(3)基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
1.6.框架架构
框架架构讲解:
(1)加载配置:配置来源于两个地方,一处是配置文件,一处是Java代码的注解,将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。
(2)SQL解析:当API接口层接收到调用请求时,会接收到传入SQL的ID和传入对象(可以是Map、JavaBean或者基本数据类型),Mybatis会根据SQL的ID找到对应的MappedStatement,然后根据传入参数对象对MappedStatement进行解析,解析后可以得到最终要执行的SQL语句和参数。
(3)SQL执行:将最终得到的SQL和参数拿到数据库进行执行,得到操作数据库的结果。
(4)结果映射:将操作数据库的结果按照映射的配置进行转换,可以转换成HashMap、JavaBean或者基本数据类型,并将最终结果返回。
1.7.动态SQL
MyBatis 最强大的特性之一就是它的动态语句功能。如果您以前有使用JDBC或者类似框架的经历,您就会明白把SQL语句条件连接在一起是多么的痛苦,要确保不能忘记空格或者不要在columns列后面省略一个逗号等。动态语句能够完全解决掉这些痛苦。
尽管与动态SQL一起工作不是在开一个party,但是MyBatis确实能通过在任何映射SQL语句中使用强大的动态SQL来改进这些状况。动态SQL元素对于任何使用过JSTL或者类似于XML之类的文本处理器的人来说,都是非常熟悉的。在上一版本中,需要了解和学习非常多的元素,但在MyBatis 3 中有了许多的改进,现在只剩下差不多二分之一的元素。MyBatis使用了基于强大的OGNL表达式来消除了大部分元素。
1.8.集成
<!--自动扫描业务包-->
<context:component-scanbase-package="com.xxx.service"/>
<!--数据源-->
<jee:jndi-lookupid="jndiDataSource"jndi-name="java:comp/env/jdbc/datasource"/>
<!--配置事务-->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<propertyname="dataSource"ref="jndiDataSource"/>
</bean>
<!--配置基于注解的事物aop-->
<tx:annotation-driventransaction-manager="txManager"proxy-target-class="true"/>
1.9Mybatis中javaType和jdbcType对应关系
JDBC 类型 | Java 类型 |
CHAR | String |
VARCHAR | String |
LONGVARCHAR | String |
NUMERIC | java.math.BigDecimal |
DECIMAL | java.math.BigDecimal |
BIT | boolean |
BOOLEAN | boolean |
TINYINT | byte |
SMALLINT | short |
INTEGER | int |
BIGINT | long |
REAL | float |
FLOAT | double |
DOUBLE | double |
ARRAY | Array |
BINARY | byte[] |
VARBINARY | byte[] |
LONGVARBINARY | byte[] |
DATE | java.sql.Date |
TIME | java.sql.Time |
TIMESTAMP | java.sql.Timestamp |
CLOB | Clob |
BLOB | Blob |
DISTINCT | mapping of underlying type |
STRUCT | Struct |
REF | ref |
DATALINK | java.net.URL[color=red][/color] |
二.第一个mybatis程序
2.1.搭建一个项目
2.1.1 搭建数据库
use test;
create table user(
id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) NOT NULL,
age INT(3) NOT NULL DEFAULT 0
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
select * from user;
INSERT INTO user1 VALUES(NULL,'张三',18),(NULL,'张四',20),(NULL,'张五',25);
2.1.2导入依赖
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
2.1.3建立实体 com.xx.entity.User.java
package com.xx.entity;
public class User {
private int id;
private String name;
private int age;
public User() {
}
public User(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
2.1.4建立mybatisUtil类 com.xx.util.MybatisUtil.java
package com.xx.util;
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 java.io.IOException;
import java.io.InputStream;
public class MybatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
2.1.5建立mapper接口 com.xx.mapper.UserMapper.UserMapper.java
package com.xx.mapper;
import com.xx.entity.User;
import java.util.List;
import java.util.Map;
public interface UserMapper {
public List<User> selectUsers();
public User getUserById(int id);
public List<User> selectUserLikeName(String name);
public int addUser(User user);
public int addUserByMap(Map map);
public int updateUserById(User user);
public int updateUserByMap(Map map);
public int deleteUserById(int id);
}
2.1.6配置mapper文件 com.xx.mapper.UserMapper.UserMapper1.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.xx.mapper.UserMapper">
<select id="selectUsers" resultType="com.xx.entity.User">
select * from test.user;
</select>
<select id="getUserById" parameterType="int" resultType="com.xx.entity.User">
select * from test.user where id = #{id}
</select>
<!--模糊查询-->
<select id="selectUserLikeName" parameterType="java.lang.String" resultType="com.xx.entity.User">
select * from test.user where name like "%"#{name}"%"
</select>
<insert id="addUser" parameterType="com.xx.entity.User">
insert into test.user values (#{id},#{name},#{age})
</insert>
<insert id="addUserByMap" parameterType="java.util.Map">
insert into test.user values (#{userId},#{userName},#{userAge})
</insert>
<update id="updateUserById" parameterType="com.xx.entity.User">
update test.user set age = #{age},name=#{name} where id = #{id}
</update>
<!--修改-->
<update id="updateUserByMap" parameterType="java.util.Map">
update test.user
<set>
<if test="userName != null" >
name = #{userName,jdbcType=VARCHAR},
</if>
<if test="userAge != null" >
age = #{userAge,jdbcType=INTEGER}
</if>
</set>
where id = #{userId,jdbcType=TINYINT};
</update>
<delete id="deleteUserById" parameterType="int">
delete from test.user where id=#{id}
</delete>
</mapper>
2.1.7mybatis-config配置文件
<?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">
<!--config核心配置-->
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&serverTimezone=UTC"/>
<property name="username" value="username"/>
<property name="password" value="password"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/xx/mapper/UserMapper1.xml"/>
<!-- <mapper class="com.xx.mapper.UserMapper"/>-->
</mappers>
</configuration>
2.1.8单元测试
package com.xx.mapper;
import com.xx.entity.User;
import com.xx.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class UserMapperTest {
public SqlSession sqlSession= MybatisUtil.getSqlSession();;
@Test
public void testUserMapper(){
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.selectUsers();
for (User user : users) {
System.out.println(user);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
}
}
@Test
public void testSelectUserById(){
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(2);
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
}
}
@Test
public void testSelectUserLikeName(){
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.selectUserLikeName("葛");
for (User user : users) {
System.out.println(user);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
}
}
/**
* 增删改需要提交事务
*/
@Test
public void testAddUser(){
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.addUser(new User(4, "asd", 20));
if (i>0){
sqlSession.commit();
}
System.out.println(i);
} catch (Exception e) {
e.printStackTrace();
sqlSession.rollback();
} finally {
sqlSession.close();
}
}
@Test
public void testAddUserByMap(){
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map map=new HashMap<String , Object>();
map.put("userId",7);
map.put("userName","诸葛");
map.put("userAge",18);
int i = mapper.addUserByMap(map);
if (i>0){
sqlSession.commit();
}
System.out.println(i);
} catch (Exception e) {
e.printStackTrace();
sqlSession.rollback();
} finally {
sqlSession.close();
}
}
@Test
public void testUpdateUserById(){
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.updateUserById(new User(4,"马六",30));
if (i>0){
sqlSession.commit();
}
System.out.println(i);
} catch (Exception e) {
e.printStackTrace();
sqlSession.rollback();
} finally {
sqlSession.close();
}
}
@Test
public void testUpdateUserByMap(){
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map map=new HashMap<String , Object>();
map.put("userId",6);
map.put("userName","诸葛孔明");
map.put("userAge",20);
int i = mapper.updateUserByMap(map);
if (i>0){
sqlSession.commit();
}
System.out.println(i);
} catch (Exception e) {
e.printStackTrace();
sqlSession.rollback();
} finally {
sqlSession.close();
}
}
@Test
public void testDeleteUserById(){
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.deleteUserById(4);
if (i>0){
sqlSession.commit();
}
System.out.println(i);
} catch (Exception e) {
e.printStackTrace();
sqlSession.rollback();
} finally {
sqlSession.close();
}
}
}
2.2 测试可能遇到问题
2.2.1 org.apache.ibatis.binding.BindingException: Type interface com.xx.mapper.UserMapper is not known to the MapperRegistry.
排查思路: mapper-config配置文件中未配置mapper.xml
2.2.1 org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.xx.mapper.xx.xx
排查思路:
1.查看mapper.xml 中 mapper属性 namespace 是否和 文件所在包名一致
2.在mapper-config配置文件中使用<package name="包名">标签时,需要映射文件名和接口名一样,不然会报错。
3.在mapper-config配置文件中使用<mapper class="">mapper标签的class属性时,需要映射文件名和接口名一样,不然会报错。
4.在mapper-config配置文件中使用<mapper resource="org/xx/demo/mapper/xx.xml"/>,不需要映射文件名和接口名一样
三.mybatis 增删改查
3.1.操作步骤
3.1.1修改mapper接口
3.1.2修改mapper.xml映射
3.1.3修改单元测试
3.2增加
3.2.1修改mapper接口
public int addUser(User user);
public int addUserByMap(Map map);
3.2.2修改mapper.xml映射
<insert id="addUser" parameterType="com.xx.entity.User">
insert into test.user values (#{id},#{name},#{age})
</insert>
<insert id="addUserByMap" parameterType="java.util.Map">
insert into test.user values (#{userId},#{userName},#{userAge})
</insert>
3.3.3修改单元测试
3.3修改
3.3.1修改mapper接口
public int updateUserById(User user);
public int updateUserByMap(Map map);
3.3.2修改mapper.xml映射
<update id="updateUserById" parameterType="com.xx.entity.User">
update test.user set age = #{age},name=#{name} where id = #{id}
</update>
<!--修改-->
<update id="updateUserByMap" parameterType="java.util.Map">
update test.user
<set>
<if test="userName != null" >
name = #{userName,jdbcType=VARCHAR},
</if>
<if test="userAge != null" >
age = #{userAge,jdbcType=INTEGER},
</if>
</set>
where id = #{userId,jdbcType=TINYINT};
</update>
3.3.3修改单元测试
3.4删除
3.4.1修改mapper接口
public int deleteUserById(int id);
3.4.2修改mapper.xml映射
<delete id="deleteUserById" parameterType="int">
delete from test.user where id=#{id}
</delete>
3.4.3修改单元测试
3.5查询
3.5.1修改mapper接口
public User getUserById(int id);
public List<User> selectUserLikeName(String name);
3.5.2修改mapper.xml映射
<select id="getUserById" parameterType="int" resultType="com.xx.entity.User">
select * from test.user where id = #{id}
</select>
<!--模糊查询-->
<select id="selectUserLikeName" parameterType="java.lang.String" resultType="com.xx.entity.User">
select * from test.user where name like "%"#{name}"%"
</select>
3.5.3修改单元测试
四.配置解析
4.1核心配置文件
4.1.1 mybatis-config.xml
configuration(配置)
- properties(属性) 这些属性可以在外部进行配置,并可以进行动态替换。如果一个属性在不只一个地方进行了配置,那么,MyBatis 将按照下面的顺序来加载:
- 首先读取在 properties 元素体内指定的属性。
- 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
- 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。
- settings(设置)这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。
- typeAliases(类型别名) 类型别名可为 Java 类型设置一个缩写名字。 仅用于 XML 配置,意在降低冗余的全限定类名书写。可以配置类的全限定名,指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean。
- typeHandlers(类型处理器)MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型
- objectFactory(对象工厂)每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。
- plugins(插件)
- environments(环境配置)
- environment(环境变量) MyBatis 可以配置成适应多种环境,例如,开发、测试和生产环境。
- transactionManager(事务管理器) MyBatis 中有两种类型的事务管理器 , type="[JDBC|MANAGED]" 默认:JDBC 。 MANAGED – 几乎没做什么,从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期。
- dataSource(数据源) c3p0 dbcp druid. 使用标准的JDBC 数据源接口来配置 JDBC 连接对象的资源,三种内建的数据源类型(type="[UNPOOLED|POOLED|JNDI]")
- UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接,有点慢。
- POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求(常用)。
- JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用。
- environment(环境变量) MyBatis 可以配置成适应多种环境,例如,开发、测试和生产环境。
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
4.2环境配置
MyBatis 可以配置成适应多种环境,例如,开发、测试和生产环境。
但: 每个SqlSessionFactory实例只能选择一个环境。
Mybatis默认事务管理器是 JDBC、 连接池。
4.3properties
db.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&serverTimezone=UTC
username=username
password=password
mybatis-config.xml中引入 db.properties
<properties resource="db.properties"/>
属性配置位置要求:
4.4类型别名(typeAliases)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
mybatis-config.xml中配置类型别名
<typeAliases>
<!-- 方法1 -->
<!--<typeAlias type="com.xx.entity.User" alias="User"/>-->
<!-- 方法2 默认别名为类的首字母小写-->
<package name="com.xx.entity"/>
</typeAliases>
4.5映射器(mappers)
mybatis-config.xml中引入 db.properties
<mappers>
<mapper resource="com/xx/mapper/UserMapper.xml"/>
<!-- <mapper class="com.xx.mapper.UserMapper"/>-->
</mappers>
注册绑定我们的mapper.xml文件,有四种配置方式
方式一(推荐使用)
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
方式二(不建议使用)
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
方式三
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
注:
1. 接口及其mapper配置文件必须同名
2.接口及其mapper配置文件必须在同一个包中
方式四
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
4.6作用域(Scope)和生命周期
不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
4.6.1 对象生命周期和依赖注入框架
依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器,并将它们直接注入到你的 bean 中,因此可以直接忽略它们的生命周期。
4.6.2 SqlSessionFactoryBuilder
- 创建了 SqlSessionFactory,就不再需要它
- 局部变量
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 最佳作用域是方法作用域(也就是局部方法变量)。
可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
4.6.3 SqlSessionFactory
- 单例模式
- 应用运行期一直存在
一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。最佳作用域是应用作用域。
可以使用单例模式或者静态单例模式。
4.6.4 SqlSession
- 相当于链接池中的一个请求
- 非线程安全
- 用完及时关闭,释放资源
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的.。 最佳的作用域是请求或方法作用域。
绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。
五.XML 映射器
MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。
SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):
cache
– 该命名空间的缓存配置。cache-ref
– 引用其它命名空间的缓存配置。resultMap
– 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。parameterMap
– 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。sql
– 可被其它语句引用的可重用语句块。insert
– 映射插入语句。update
– 映射更新语句。delete
– 映射删除语句。select
– 映射查询语句。
5.1 select
查询语句是 MyBatis 中最常用的元素之一——光能把数据存到数据库中价值并不大,还要能重新取出来才有用,多数应用也都是查询比修改要频繁。 MyBatis 的基本原则之一是:在每个插入、更新或删除操作之间,通常会执行多个查询操作。因此,MyBatis 在查询和结果映射做了相当多的改进。一个简单查询的 select 元素是非常简单的。例:
<select id="getUserById" parameterType="int" resultType="User">
select * from user where id = #{id}
</select>
语句名为 getUserById,接受一个 int(或 Integer)类型的参数,并返回一个 User类型的对象,其中的键是列名,值便是结果行中的对应值。
5.2 insert, update 和 delete
数据变更语句 insert,update 和 delete 的实现非常接近 例:
<insert id="addUser">
insert into taotao.user values (#{id},#{name},#{age})
</insert>
<insert id="addUserByMap" parameterType="java.util.Map">
insert into taotao.user values (#{userId},#{userName},#{userAge})
</insert>
<update id="updateUserById" parameterType="User">
update taotao.user set age = #{age},name=#{name} where id = #{id}
</update>
<update id="updateUserByMap" parameterType="java.util.Map">
update taotao.user
<set>
<if test="userName != null" >
name = #{userName,jdbcType=VARCHAR},
</if>
<if test="userAge != null" >
age = #{userAge,jdbcType=INTEGER}
</if>
</set>
where id = #{userId,jdbcType=TINYINT};
</update>
<delete id="deleteUserById" parameterType="int">
delete from user where id=#{id}
</delete>
如果你的数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),那么你可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置为目标属性就 OK 了。例如,如果上面的 Author 表已经在 id 列上使用了自动生成,那么语句可以修改为:
<insert id="addUser" useGeneratedKeys="true" keyProperty="id">
insert into user (name,age) values (#{name},#{age})
</insert>
5.3 参数
5.3.1 字符串替换
.默认情况下,使用 #{}
参数语法时,MyBatis 会创建 PreparedStatement
参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。
不过有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串。 比如 ORDER BY 子句,这时候你可以使用${}:,这样,MyBatis 就不会修改或转义该字符串了。
ORDER BY ${columnName}
当 SQL 语句中的元数据(如表名或列名)是动态生成的时候,字符串替换将会非常有用。例如:
@Select("select * from user where id = #{id}")
User findById(@Param("id") long id);
@Select("select * from user where name = #{name}")
User findByName(@Param("name") String name);
@Select("select * from user where email = #{email}")
User findByEmail(@Param("email") String email);
可以替换成:
@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);
其中 ${column}
会被直接替换,而 #{value}
会使用 ?
预处理。
注:用这种方式接受用户的输入,并用作语句参数是不安全的,会导致潜在的 SQL 注入攻击。
5.4 结果映射(resultMap)
可以解决数据库字段名和实例属性不一致情况。
实例:
public class User {
private int id;
private String userName;
private int age;
...
}
结果映射:(resultMap)
<resultMap id="userMapping" type="user">
<!-- <result column="id" property="id"/>-->
<result column="name" property="userName"/>
<!-- <result column="age" property="age"/>-->
</resultMap>
<select id="selectUsers" resultMap="userMapping">
select * from user;
</select>
<select id="getUserById" parameterType="int" resultMap="userMapping">
select * from user where id = #{id}
</select>
六.关联查询
6.1搭建测试环境,建表
create table teacher(
tid int(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
tname VARCHAR(30) NOT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8;
--drop table teacher;
insert into teacher values (1,'张老师');
create table student(
sid int(10),
sname varchar(20),
age int(3),
gender varchar(10),
teacherid int(10),
constraint fktid foreign key(teacherid) references teacher(tid)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
insert into student values (1,'张三',18,'男',1);
insert into student values (2,'公孙倩影',18,'男',1);
insert into student values (3,'赵云龙',18,'男',1);
insert into student values (4,'李雪',17,'女',1);
select * from student;
--show tables;
6.1.1 创建实体
package com.xx.entity;
public class Student {
private int sid;
private String sname;
private int age;
private String gender;
private Teacher teacher;
public Student() {
}
public Student(int sid, String sname, int age, String gender, Teacher teacher) {
this.sid = sid;
this.sname = sname;
this.age = age;
this.gender = gender;
this.teacher = teacher;
}
public int getSid() {
return sid;
}
public void setSid(int sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
@Override
public String toString() {
return "Student{" +
"sid=" + sid +
", sname='" + sname + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", teacher=" + teacher +
'}';
}
}
package com.xx.entity;
public class Teacher {
private int tid;
private String tname;
public Teacher() {
}
public Teacher(int tid, String tname) {
this.tid = tid;
this.tname = tname;
}
public int getTid() {
return tid;
}
public void setTid(int tid) {
this.tid = tid;
}
public String getTname() {
return tname;
}
public void setTname(String tname) {
this.tname = tname;
}
@Override
public String toString() {
return "Teacher{" +
"tid=" + tid +
", tname='" + tname + '\'' +
'}';
}
}
6.1.2创建mapper接口
package com.xx.mapper;
import com.xx.entity.Student;
import java.util.List;
public interface StudentMapper {
public List<Student> selectStudents();
public List<Student> getStudents();
}
package com.xx.mapper;
import com.xx.entity.Teacher;
import org.apache.ibatis.annotations.Param;
public interface TeacherMapper {
public Teacher selectTeacherById(@Param("tid") int id);
}
6.1.3mybatis链接
package com.xx.util;
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 java.io.IOException;
import java.io.InputStream;
public class MybatisUtil {
private static SqlSessionFactory sessionFactory;
static{
String resource="mybatis-config.xml";
try {
InputStream inputStream = Resources.getResourceAsStream(resource);
sessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
return sessionFactory.openSession();
}
}
6.1.4 mybatis属性配置文件
<?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="db.properties"/>
<settings>
<setting name="logPrefix" value="mybatis日志打印:"/>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<typeAliases>
<package name="com.xx.entity"/>
<!-- <typeAlias type="com.xx.entity.Student" alias="Student"/>
<typeAlias type="com.xx.entity.Teacher" alias="Teacher"/>-->
</typeAliases>
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/xx/mapper/StudentMapper.xml"/>
<mapper resource="com/xx/mapper/TeacherMapper.xml"/>
<!-- <mapper class="com.xx.mapper.TeacherMapper"/>
<mapper class="com.xx.mapper.StudentMapper"/>-->
</mappers>
</configuration>
6.1.5 数据库参数属性
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&serverTimezone=UTC
username=root
password=root
6.2 多对一查询
6.2.1基于结果查询
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xx.mapper.StudentMapper">
<select id="getStudents" resultMap="StudentInf">
SELECT s.sid id,s.sname name,s.age,s.gender,t.tname name from student s,teacher t where s.teacherid=t.tid;
</select>
<resultMap id="StudentInf" type="Student">
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
</mapper>
6.2.1基于嵌套查询
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xx.mapper.StudentMapper">
<select id="selectStudents" resultMap="StudentInfo">
select * from student;
</select>
<resultMap id="StudentInfo" type="Student">
<association property="teacher" column="teacherid" javaType="Teacher" select="selectTeacherById"/>
</resultMap>
<select id="selectTeacherById" parameterType="int" resultType="Teacher">
select * from teacher where tid=#{tid}
</select>
</mapper>
6.3 一对多查询
6.3.1环境搭建
.。。
6.3.2创建实例
package com.xx.entity;
public class Student {
private int sid;
private String sname;
private int age;
private String gender;
private int stid;
public Student() {
}
public Student(int sid, String sname, int age, String gender, int stid) {
this.sid = sid;
this.sname = sname;
this.age = age;
this.gender = gender;
this.stid = stid;
}
public int getSid() {
return sid;
}
public void setSid(int sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getTeacherid() {
return stid;
}
public void setTeacherid(int teacherid) {
this.stid = teacherid;
}
@Override
public String toString() {
return "Student{" +
"sid=" + sid +
", sname='" + sname + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", stid=" + stid +
'}';
}
}
package com.xx.entity;
import java.util.List;
public class Teacher {
private int tid;
private String tname;
private List<Student> students;
public Teacher() {
}
public Teacher(int tid, String tname, List<Student> students) {
this.tid = tid;
this.tname = tname;
this.students = students;
}
public int getTid() {
return tid;
}
public void setTid(int tid) {
this.tid = tid;
}
public String getTname() {
return tname;
}
public void setTname(String tname) {
this.tname = tname;
}
public List<Student> getStudents() {
return students;
}
public void setStudents(List<Student> students) {
this.students = students;
}
@Override
public String toString() {
return "Teacher{" +
"tid=" + tid +
", tname='" + tname + '\'' +
", students=" + students +
'}';
}
}
6.3.3创建mapper映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xx.mapper.TeacherMapper">
<select id="selectTeacherById" resultMap="TeacherInfo">
select * from teacher t,student s where t.tid=#{tid} and t.tid=s.teacherid
</select>
<resultMap id="TeacherInfo" type="Teacher">
<result property="tid" column="tid"/>
<result property="tname" column="tname"/>
<collection property="students" ofType="Student">
<result property="sid" column="sid"/>
<result property="sname" column="sname"/>
<result property="age" column="age"/>
<result property="gender" column="gender"/>
<result property="stid" column="teacherid"/>
</collection>
</resultMap>
</mapper>
七. 动态SQL
根据不同的标签生成需要的sql
7.1 choose、when、otherwise
根据条件选择其中一个条件执行,类似java 的 switch case语句
mapper示例
<select id="selectStudentsChoose" parameterType="map" resultType="Student">
select * from student where teacherid=1
<choose>
<when test="sname !=null">
AND sname like "%"#{sname}"%"
</when>
<when test="age !=null">
AND age=#{age}
</when>
<when test="gender !=null">
AND gender=#{gender}
</when>
<otherwise>
AND sid=1
</otherwise>
</choose>
</select>
7.2 trim、where、set
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
<select id="selectStudentsWhere" parameterType="map" resultType="Student">
select * from student
<where>
<if test="sname !=null">
AND sname like "%"#{sname}"%"
</if>
<if test="age !=null">
AND age=#{age}
</if>
</where>
</select>
如果 where 元素与期望的不太一样,可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:
<update id="updateStudentById" parameterType="map">
update student
<set>
<if test="sname !=null">sname = #{sname,jdbcType=INTEGER},</if>
<if test="age !=null">age = #{age,jdbcType=VARCHAR},</if>
<if test="gender !=null">gender = #{gender,jdbcType=VARCHAR},</if>
<if test="teacherid !=null">teacherid = #{teacherid,jdbcType=INTEGER}</if>
</set>
where sid = #{sid}
</update>
7.3 foreach
对集合遍历,可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
<select id="selectStudentsIn" parameterType="list" resultType="Student">
select * from student
<where>
sid in
<foreach collection="list" index="index" item="sid" open="(" close=")" separator=",">
#{sid}
</foreach>
</where>
</select>