Java – MyBatis 基础使用
简介
MyBatis 是Java中用于解决sql数据库操作的第三方包插件,在平时使用JDBC进行数据库操作时,都会把sql写在代码中,这样的问题是使得后期的代码维护变得困难和复杂,而MyBatis则是为了解决把sql语句写死在代码中的问是,并提高了代码的解偶性。
MyBatis 安装
通过Maven安装,在pom.xml文件上增加一个坐标
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>x.x.x</version>
</dependency>
不使用Maven安装,需要下载jar包导入即可。
https://mvnrepository.com/artifact/org.mybatis/mybatis
IDEA插件安装
IDEA中提供了一个非常实用的插件 MyBatisX
因为在实际开发过程中,我们会使用 Mapper代理 进行开发,在开发过程中,我们会经常游走在 Mapper 接口 和 Mapper.xml 来回对比,而MyBatisX插件则很好地实现了两者的导向,和Mapper接口中新增抽象方法时,能自动在Mapper.xml中创建对应的sql操作标签。
创建配置文件
要使用mybatis之前,我们需要配置一个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 标签可以指定多个数据源地址,方便在生产环境或测试环境中切换不同数据源 -->
<environments default="development">
<!-- 设置名为development的数据源-->
<environment id="development">
<!-- 指定使用的数据源管理,通常是JDBC-->
<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="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
保存为 mybatis-config.xml 存放在项目java源码resources文件夹中。
<?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 标签可以指定多个数据源地址,方便在生产环境或测试环境中切换不同数据源 -->
<environments default="development">
<!-- 设置名为development的数据源-->
<environment id="development">
<!-- 指定使用的数据源管理,通常是JDBC-->
<transactionManager type="JDBC"/>
<!-- 设置数据源池-->
<dataSource type="POOLED">
<!-- 数据源账号密码-->
<!-- mysql5以前使用com.mysql.jdbc.Driver-->
<!-- mysql8使用com.mysql.cj.jdbc.Driver-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!-- mysql8需要添加UTC时区-->
<property name="url" value="jdbc:mysql://localhost:3306/jdbc?serverTimezone=UTC"/>
<property name="username" value=""/>
<property name="password" value=""/>
</dataSource>
</environment>
</environments>
<!-- 类和数据的映射,可以使查询的数据对应类中的成员变量--> <mappers>
<mapper resource=""/>
</mappers>
</configuration>
创建SQL映射文件(Mapper)
映射文件即指定某个数据表和类的关系映射,在mybatis中,每一个数据表或每一个查询数据结果都可以看作是一个类的实例,而每一个字段的数据则是对象的成员变量。
这里需要提供两个文件,一个是以xxxMapper.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="userNamespace">
<select id="selectAll" resultType="cn.unsoft.pojo.User">
select * from user where id = 1
</select>
</mapper>
其中 select 标签则表示这个标签包含查询语句,其它标签还有 <update>,<insert>,<delete>等等
id 值表示要执行的操作,在java中可以通过id值调用mapper文件中指定的区块的sql语句。
resultType
值表示执行该sql后,数据表中的字段值应对应那一个类。
IDEA映射表警告
关于在IDEA中mapper.xml文件中定义sql语句时数据表名标红的问题,这是因为IDEA没有连接数据库,因此不认识数据库表名,只要在IDEA中连接数据库后就可以,并且还支持智能提示
另一个是用于存放查询结果数据的类
public class User {
// 数据表中的字段,成员变量名称需要和字段名称一致
String username;
Integer age;
String address;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
在 Mybatis-config.xml文件中把mapper区块中设定映射文件
<mappers>
<mapper resource="UserMapper.xml"/>
<mapper resource="OrderMapper.xml"/>
...
</mappers>
构建 SqlSessionFactory
1.取得SqlSessionFactory
通过调用mybatis的方法构建SqlSessionFactory
,然后通过SqlSessionFactory
生成SqlSession
会话,我们通过这个会话就可以执行各个mapper文件中的sql查询了,并把所有查询赋到映射的类中。
// 文件存放在resource文件夹中
String resource = "mybatis-config.xml";
// 通过由mubatis提供的Resources类方法获取输入汒
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
2.取得SqlSession
SqlSession session = sqlSessionFactory.openSession()
3.执行sql查询
List<User> users = session.selectList("命名空间.id");
List<User> users = session.selectList("userNameSpace.selectAll");
4.释放资源
session.close();
关于查询字段名和成员名不一致导致数据无法赋值的解决方法
有时候我们在数据库中定义的字段名可能不一致,比如下面的例子
public class User {
String username;
Integer age;
String address;
}
而数据库中的数据表为
varchar user_name
int age
varchar add
此时就有可能出现成员变量 username 和 address 不能正确的赋值。
解决方法一:别名
可以通过在sql中定义别名来避免这个问题
<select id="selectBlog" resultType="cn.unsoft.pojo.User">
select user_name as username,age,add as address from user where id = 1
</select>
缺点是:sql语句变得冗余,代码块很长,且不好维护
解决方法二:使用 sql 代码块
mybatis 提供 <sql> 标签写法,意在可以单独定义 sql 语句,一次定义在多个地方复用
// 定义 sql 语句块
<sql id="xxxx">
sql 语句
<sql>
// 引用 sql 语句块
<include refid="xxx" />
举例
<sql id="user_column">
id,username,password,address
<sql>
// 引用上面的 sql 语句块
<select id="selectUser" resultType="User">
select
<include refid="xxx" />
from user;
</select>
sql语句相当于:select id,username,password,address from user;
方法三:resultMap
请参照本文 resultMap 章节
方法四:Setting中设置转换驼峰
<settings>
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
注意:这个设置只限用于把 _ 转为驼峰转换
Mapper代理
mybatis的作用是在于让写死在java代码上的sql语句等等硬编码改成灵活的文件式配置,通过修改配置文件的方式就可以不通过修改java代码的基础上进行sql语句的维护,但通过上面的普通配置,我们发现在调用sql查询时,我们依然存在硬编码的情况。
List<User> users = session.selectList("userNameSpace.selectAll");
因此我们可以通过另一种更好的方法来操作数据库。
定义Mapper代理
1.定义与SQL映射文件同名的Mapper接口,并且将Mapper接口和SQL映射文件放置在同一目录下。
定义一个和Mapper同名的接口。
2.设置SQL映射文件的namespace
属性为Mapper接口全限定名。
在 UserMapper.xml 中设定 namespace
为全域名
<mapper namespace="cn.unsoft.Mybatis.Mapper.UserMapper">
...
</mapper>
3.在Mapper接口中定义方法,方法名就是sql映射文件中的sql语句的id,并保持参数类型和返回值类型一致
public interface UserMapper {
// 接口上的抽象方法和 mapper 的id 相同,返回做也相同
List<User> selectAll();
}
<mapper namespace="cn.unsoft.Mybatis.Mapper.UserMapper">
<!-- resultType 的值为返回值,和接口相同 -->
<select id="selectAll" resultType="cn.unsoft.Mybatis.pojo.User">
sql语句
</select>
</mapper>
注意:因为查询会出现多个结果,使用List集合封装,所以接口中的返回值必须为List<User> 集合,返回值要根据查询的语意来判断返回值。
3.1.在mapper.xml 中,定义sql语句时,因XML的限制,会和部分符合发生冲突导致出错,例如
<select id="selectAll" resultType="User">
// 【<】符号报错,原因是【<】号是XML的标签定义开头符
select * from user where id < 1
</select>
解决方法有两种:
方法一:转义字
使用 < 代替【<】 符,< 是【<】的html转义字
<select id="selectAll" resultType="User">
// 使用 < 转义
select * from user where id < 1
</select>
方法二:使用 [!CDATA[]]
[!CDATA[]] 是用在XML来一般表示万能输出,即在 [!CDATA[ ]] 所包含在内的内容都会以原文输出。IDEA中键入【CD】能智能填充
<select id="selectAll" resultType="User">
// 使用 [!CDATA[ ]] 输出原文
select * from user where id [!CDATA[ < ]] 1
</select>
3.2.传值占位符
平常在数据库查询中,都会传入条件数据进行查询,在Mybatis 中,传入条件数据使用占位符,而占位符有两种
#{prop}
: 这一种占位符会触发Mybatis调用 PreparedStatement
类,会先让sql语句中的条件数据以【?】占位,再把数据导入查询,起到防止SQL注入的目的,在项目中基本使用这种占位符
${prop}
: 会触发Mybatis调用普通的 Statement
类,直接把条件数据拼到sql语句中,有SQL注入的风险,这种类型的占位符一般用在内部,如动态表名等等
<select id="selectById" resultType="User">
// 使用 #{id} 占位符时,则可在接口中定义接收参数
select * from user where id = #{id}
</select>
同时在Mapper.xml中也可以强制定义接收类型,但因Java中使用接口,已经强制接收指定类型,所以可写可不写
<select id="selectById" parameterType="int或integer" resultType="User">
// 使用 #{id} 占位符时,则可在接口中定义接收参数
select * from user where id = #{id}
</select>
interface Mapper {
// 形参与占位符不互相对应,关于多参数传递可看多参数传递章节
User selectById(int id)
}
3.3. 在 mybatis-config.xml 文件中要记得把 UserMapper.xml 导入
<mappers>
<!-- 加载sql的映射文件-->
<mapper resource="cn/unsoft/Mybatis/Mapper/UserMapper.xml"/>
</mappers>
4.1.通过 SqlSession
的 getMapper
方法获取Mapper接口的代理对象
// 定义mybatis-config文件
String mybatis = "mybatis-config.xml";
// 使用 mybatis 提供的Resources类,取得在Resource文件夹中的文件流
InputStream myb = Resources.getResourceAsStream(mybatis);
// 读取配置文件,并生成SqlSessionFactory类
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(myb);
// 从SqlSessionFactory工厂中打开一条sql连接会话
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过 getMapper() 反射读取到 UserMapper 接口中的方法
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
4.2.调用对应方法完成sql的执行。
/*
* 至止,Mybatis 通过 getMapper() 方法获得 UserMapper 接口的抽象方法
* 并对 UserMapper.xml 中的标签结构进行绑定,当我们调用接口中的 selectAll()
* 方法时,Mybatis 会寻找到 UserMapper.xml 的对应 id 的sql 语句并执行。
* */
// 此时,通过调用UserMapper中的抽象方法
List<User> users = userMapper.selectAll();
4.3 释放资源
sqlSession.close();
5.如果Mapper接口名称和SQL映射文件名称相同,并在同一目录下,则可以使用包扫描的方式简化SQL映射文件的加载,这样就可以把所有 Mapper 放在里面,通过包扫描的方式自动导入Mapper.xml文件了
<mappers>
<!-- 加载sql的映射文件-->
<!-- <mapper resource="cn/unsoft/Mybatis/Mapper/USerMapper.xml"/>-->
<package name="cn.unsoft.Mybatis.Mapper"/>
</mappers>
我们认为,java 代码应该放在java文件夹中,而xml文件存放在resources文件夹中。在实际开发中,当文件编译完成后,resources目录下的同名文件夹会和java下的同名文件夹合并在一起,也就是说,只要我们在resources和java文件夹中创建相同目录结构的文件夹,在编译后都会放在一起.
查询数据<select>
在项目中我们肯定少不了单个条件参数查询和多个参数查询的情况,甚至有动态多参数查询的情况。
单参数查询
单参数查询指的是一条sql查询语句中,只传入一个查询条件,如通过ID值查询商品信息等,单参数查询可通过简单方法进行传递。
在<select> 查询标签中使用占位符定义
<select id="selectById" resultType="User">
// 使用 #{id} 占位符时,则可在接口中定义接收参数
select * from user where id = #{id}
</select>
而在Mapper接口中的抽象方法中同样定义接收一个参数即可。
interface Mapper {
selectById(int id)
}
注意:Mapper接口中的形参与<select>中的 #{id}
互不相关,它们没有绑定关系,仅因为接口只接收一个参数,而 <select> 中也只有一个占位符,因此Mybatis能自动识别。
多参数查询
Mybatis 在多参数查询中提供了三种参数绑定方式:
1.通过散装传递
当一条查询语句中需要有多个参数进行查询时,Mybatis只知道传递过来的参数值,但无法识别Mapper接口抽象方法中的形参识别绑定,如果参数只有一个时,Mapper.xml占位符中的属性可以是任意的,如果参数是多个时,Mytabis会把参数打包成一个Map类型传进来,
[arg0="参数1数据", arg1="参数2数据", param1="参数1数据", param2="参数2数据"]
<select id="selectAll" resultType="User">
select * from user where id = #{id} and username = #{username} and age = #{age}
</select>
Mybatis支持使用注以注解的方式自定义Map的key名 ,Mapper接口通过@Param
注解方式对占位符进行绑定
List<User> selectAll(@Param("id") int id, @Param("username") String username, @Param("age") String age);
注意:我们建议,当使用单个参数时,都这该加入 @param 注解以定义属性名。
2.通过类对象传递
在查询数据库类中得出结果,可以映射成类,同样的,我们也可以通过映射类的对象,绑定占位符
// 定义抽象方法的参数为类对象
List<User> selectAll(User user);
// 定义映射类对象
User u = new User();
u.setId(1);
u.setUsername("张三");
u.setAge(18);
// 传入对象实例
List<User> users = userMapper.selectAll(u);
注意:关于Mapper.xml中对应的占位符名称和实体类中的成员关系。
Mapper.xml中占位符名称的标准并非在实体类中寻找对应的成员变量,而是在实体类中找到 getXxx 和 setXxx 方法中的【Xxx】名称,并把名称变为小写,该名称才是Mapper.xml中占位符中所认为的SQL属性名。
所以在实体类中,可以不存在与SQL属性名一致的成员变量,但必须要有和SQL属性名一致的get,set对应的方法。
3.通过Map传递
通过使用HashMap包装Key和Value的方式绑定占位符
// 接口中的抽象方法定义为 Map类型
List<User> selectAll(Map map);
<select id="selectAll" resultType="User">
select * from user where id= #{id} and username = #{username} and password= #{password}
</select>
// 创建 Map 对象
Map<String,Object> u = new HashMap<>();
// 传入 key 和 value 值,key须与占位符名称一致
u.put("id",1);
u.put("username","张三");
u.put("age",18);
List<User> users = userMapper.selectAll(u);
返回值类型说明
Mybatis 中设置的返回值类型可以是多样的
返回 int 类型
对于 Mybatis 而言,它事先帮我们把Java常用的类型设置了别名,比如查询记录数等sql,需要返回 int
类型,或 integer
类型
Java => mybatis
int => _int
int => _integer
Integer => int
Integer => integer
查询返回 int
类型
<select id="selectAll" resultType="int">
select count(*) from user
</select>
返回 Map 类型
我们可以用实体类去接收sql查询的数据,但是如果sql查询的数据中,没有对应的实体类时,我们可以使用Map类进行接收。
<select id="selectAll" resultType="map">
select username,password,age from user
</select>
注意:Map和实体类的区别在于,当数据表中的字段为NULL
时,Map不会把字段加入进去,Map只会记录有数据的字段。而实体类,会设置成员数据为Null
返回多条数据的Map类型
当查询的sql为多条数据时,使用单个Map会直接报错,因为Map类型只能保存一条数据的内容,key为字段名,value为字段值。
但不是说不能解决这样的问题,Mybatis可以允许一整条数据值封装成一个二维Map对象,并存放到一个一维Map对象中,但二维Map对象作为value值,必须指定一个key值。
Mybatis 提供一种注解方式 @Mapkey()
定义key值
@Mapkey("id")
public Map<String,Object> selectAll();
// 指定的key值是 id 值
// 返回的Map将以记录的 id 值作为 key,记录的数据封装成Map后作为Value
{
1={查询记录1},
2={查询记录2},
3 ....
}
返回多条数据的List<Map>类型
通过把一行记录封装成Map对象后,如果有多行数据,则把多行Map对象封装到List集合中,这种方法比较常用。
List<Map<String,Object>> selectAll();
返回数据为
[
{查询记录1},
{查询记录2},
....
]
特殊查询下的占位符问题
我们知道,Mybatis可以使用两种占位符,分别是 #{}
和 ${}
#{}
使用的是占位符赋值,会先把sql语句转为【?】
后,再给【?】
赋值,系统自动给属性值加上单位号。
${}
使用的是直接赋值,会直接把sql语句直接赋为属性值。
在某些情况下,#{}
占位符会失效。
1.模糊搜索LIKE
在使用模糊搜索时,使用 #{}
会失效。
// 会报错
<select id="selectAll" resultType="map">
select * from user where username like '%#{keyword}%'
</select>
报错原因:因为Mybatis在处理sql时,会把 #{}
占位符先转为【?】
做占位,但由于【?】
位置存放在【''】
引号包裹之下,JDBC不会把 '?'
进行转义,导致出错。
select * from user where username like '%#{keyword}%'
==> select * from user where username like '%?%'
==> 因为 '%?%' 是一个完整的字符串,JDBC不会转义字符串中的【?】,只作为单纯的字符串符号?存在
解决方法:
// 解决方法一:使用 ${} 代替
<select id="selectAll" resultType="map">
select * from user where username like '%${keyword}%'
</select>
// 解决方法二:使用Mysql函数 concat 字符拼接
<select id="selectAll" resultType="map">
select * from user where username like concat('%', #{keyword}, '%')
</select>
// 解决方法三:使所双引号隔离
<select id="selectAll" resultType="map">
select * from user where username like “%”#{keyword}"%"
</select>
2.自定义表名查询
如果表名不固定,表名需要被传入时,使用 #{}
会失效
// 会报错
<select id="selectAll" resultType="map">
select * from #{tableName}
</select>
报错原因:因为使用 #{}
进行占位时,系统会自动帮我们添加单引号,而在Mysql中查找单引号包裹的表名是不合法的
select * from #{tableName}
==> select * from 'user'
==> 不合法的表名 'user'
解决方法:
// 使用 ${} 占位符
<select id="selectAll" resultType="map">
select * from ${tableName}
</select>
3.批量删除
sql中使用批量删除,需要拼接多个属性值,会使 #{}
失效
// 删除id为 3,4,5 的记录
<delete id="deleteUser">
delete user where id in (3,4,5)
</delete>
==>
// 会报错
delete user where id in (#{ids})
报错原因:因为 #{}
会自动帮我们添加单引号,在处理占位符号,会得出错误的参数
// 我们预计中的参数拼接
delete user where id in (3,4,5)
==> 实际发生的参数拼接
delete user where id in (‘3,4,5’)
解决方法:使用 forEach
标签,或使用 ${}
占位符
动态条件查询
从上面的多条件查询我们可以解决了多个条件查询时传参的问题,但在项目中,用户有可能不会多个条件都会填写,可能只会填写一个或若干个,甚至一个条件都不写,这时需要智能匹配条件组合sql语句。
Mybatis框架的动态SQL技术是一种根据特定条件动态拼接SQL语句的功能,它存的意义是为了解决拼接SQL语句字符串时的痛点问题。
if标签
Mybatis 支持 if
标签判断,能实现判断该参数是否有值,如果没有值,则不会拼接到sql中。
使用 if 标签判断参数是否有值,如果有则拼接sql
<select id="selectAll" resultType="User">
select *
from user
where
<if test="id != null and id !='' ">
id = #{id}
</if>
<if test="username != null and username != ''">
and username = #{username}
</if>
<if test="age != null and age != ''">
and age = #{age}
</if>
</select>
这样就可以判数根据用户是否有填写条件数据来拼接sql
trim 标签
在某些情况下,我们的sql拼接时的【and】或【or】关键字可以跟在上一个条件的后面,比如下面的例子:
<select id="selectAll" resultType="User">
select *
from user
where
<if test="id != null and id !='' ">
【and】放在后面进行拼接
id = #{id} and
</if>
<if test="username != null and username != ''">
username = #{username} and
</if>
</select>
此时的【where】标签是无法识别并去掉的,就可以使用【trim】标签自定义增加或去除前后多余的内容
<select id="selectAll" resultType="User">
select *
from user
// 在前面增加一个【where】,在后面去除 【and】
<trim prefix="where" suffixOverrides="and">
<if test="id != null and id !='' ">
id = #{id} and
</if>
<if test="username != null and username != ''">
username = #{username} and
</if>
</trim>
</select>
prefix
: 在sql之前增加内容
suffix
: 在sql之后增加内容
prefixOverrides
: 在sql之前去掉内容
suffixOverrides
: 在sql之后去掉内容
where 标签
从 if
标签中我们可以看出一个问题,就是当 id
的值没有被传进来时,sql语句的拼接就会出错
假如没有id传值进来,那么sql语句将会出现如下拼接
select * from user where and username = ? and age = ?
显示 where
后面跟上 and
关键词是错误的。
解决方法一:使用恒等式,如 1=1
在 where
后面添加一个辅助的恒等式,使得所有拼接条件都将需要 and
关键词进行拼接
使用恒等式
<select id="selectAll" resultType="User">
select *
from user
where 1 = 1
<if test="id != null and id !='' ">
and id = #{id}
</if>
<if test="username != null and username != ''">
and username = #{username}
</if>
<if test="age != null and age != ''">
and age = #{age}
</if>
</select>
解决方法二:where 标签
Mybatis 考虑到这个问题,所以提供了 <where> 标签功能。
<where> 标签功能是通过配合 <if> 标签智能识别拼接条件参数是否在where
后面,并自动补全 and
关键词
<select id="selectAll" resultType="User">
select *
from user
<where>
<if test="id != null and id !='' ">
id = #{id}
</if>
<if test="username != null and username != ''">
and username = #{username}
</if>
<if test="age != null and age != ''">
and age = #{age}
</if>
</where>
</select>
当 #{id}
值没有传进来时,<where> 标签会自动识别其它拼接是否需要 and
关键词,如果在不合适的位置出现了 and
关键词, <where> 会智能去掉。
1.若where标签中有条件成立,会自动生成where关键字
2.会自动将where标签中内容前多余的and去掉,但是其中内容后多余的and无法去掉,如果要去掉后面的and,需要使用trim标签
3.若where标签中没有任何一个条件成立,则where没有任何功能
单参数多条件查询
在项目中可能会有一种需求,就是单个参数但是包含多种条件。
举例:淘宝搜索框中,只需要输入关键词(单参数),但可选择搜索商品、店铺或博文(多条件)。
也就是说,这个查询参数,不一定局限在某一个字段的数据,而是用户通过灵活选择对关键词作出不同字段的查询。
choose<when,otherwise> 标签
choose 标签类似于 Java 的switch..case的作用,可以满足单个关键词搜不同字段的问题。
<choose> : 选择会可能有值的字段
<when> : 判断,当这个字段有值,则拼接这一字段的sql片断
<otherwise> : 当所有 <when> 标签中包含的字段中都没有值时,则拼接这个sql片段
<select id="selectAll" resultType="User">
select *
from user
<where>
<choose>
<when test="id != null and id !='' ">
如果用户选择查询id,那么则会拼接这里
and id = #{keyword}
</when>
<when test="username != null and username != ''">
如果用户选择查询用户名,那么则会拼接这里
and username = #{keyword}
</when>
<when test="age != null and age != ''">
如果用户选择查询年龄,那么则会拼接这里
and age = #{keyword}
</when>
<otherwise>
如果用户所有字段都没有选中,那可以看作查询全部
1 = 1
</otherwise>
</choose>
</where>
</select>
sql标签
sql标签是一种可复用的sql片段,它可以先定义一段sql的片段,然后在需要使用这个片段的地方引用就可以了。
// 定义一个sql片段
<sql id="userColumns">
id,username,password,age
</sql>
// 在需要使用sql片段的时候引入
<select id="userSelect" resultType="User">
select <include refid="userColumns"></include> from user
</select>
添加数据<insert>
添加一行数据到数据库中,因为需要添加。所以Mapper接口声明时无需返回值,可使用类的对象进行添加。
无id值
如果我们只希望添加数据而无需知道添加后的最新 id 值时,我们只要简单配置 <insert> 标签即可
<insert id="addOne">
insert into xxx() values ()
</insert>
Mapper接口定义
void addOne(User user);
有id值返回
如果我们在对数据库中新增一行数据后希望能得到这行新数据所在的 id 值时,需要在 <insert> 中开启返回id值的属性,并指定返回的 id 字段的名称
<insert id="addOne" useGeneratedKeys="true" keyProperty="id">
insert into xxx() values ()
</insert>
Mapper接口定义
void addOne(User user);
获取id值
当我们执行完添加数据后,如果我们需要对最新添加的数据获取 id 值,可以在我们先前传入的 User 对象实例中获取
// 定义一个 User 实例
User u = new User();
u.setUsername("张三");
u.setAge(18);
u.setAddress("广东");
// 添加一个行新的数据
userMapper.addOne(u);
sqlSession.commit();
// 如果我们在 <insert> 中开启了返回 id 值的功能,那么当我们进行数据添加后,退可在原先的 User 对象中获得id值
int id = u.getId();
Mapper.xml中定义返回id值属性 useGeneratedKeys
和keyProperty
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into user
values (null, 'admin', '123456', '3000')
</insert>
keyProperty
值指的是实体类中的用于存放id的成员变量,或set。也就是说,如果 keyProperty="id"
时,Mybatis 会找到实体类中的 setId()
方法,并传入获取到的新增行 id 值(或主键值)。
调用添加方法
// 创建一个类对象填充数据
User u = new User();
u.setUsername("张三");
u.setAge(18);
u.setAddress("广东");
// 调用抽象方法
userMapper.addOne(u);
注意:MyBatis 默认开启事务处理机制,也就是说,一切的增删改都会先丢到事务中保存,执行增加数据后,需要手动执行事务提交操作
sqlSession.commit();
或者可以在生成 Session 时设定自动事务处理,这样的话每一次的增删改都会直接写到数据库中,无需手动 commit
提交。
// 从SqlSessionFactory工厂中打开一条sql连接会话,并使用自动事务提交
SqlSession sqlSession = sqlSessionFactory.openSession( true );
修改数据<update>
静态数据修改
静态数据修改是指当我们需要对一条记录需要修改时,往往会整条记录中的所有字段都会修改一遍:
// 定义数据修改 Mapper
<update id="update">
update xxx
set
username = #{userName},
age = #{age},
address = #{add}
where id = #{id}
</update>
Mapper 接口中定义抽象方法
void update(User user); ==> 执行修改不返回影响行数
int update(User user); ==> 执行修改返回影响行数
动态数据修改
在通常情况下,我们需要修改一条记录并非必须修改这条记录中的所有数据,而如果类对象中因不需修改的数据而不存在时,Mybatis会作入 null
看待,使用静态修改的话,会导致这一行记录中不需修改的字段都被修改为null
// 定义一个对象,使id 为1的记录的age改为18
User u = new User();
u.setId(1);
u.setAge(18);
userMapper.update(u);
// 这会使得这条记录的其它字段将更新为 null
<set> 标签
Mybatis 中考虑到这个问题,提供 <set> 标签,配合 <if> 标签智能拼接 sql 语句
<update id="update">
update xxx
// 使用 <set> 标签配合 <if> 标签能避免出现符号拼接问题,<set> 会自动添加或去除【,】符
<set>
<if test="username != null and username != ''">username = #{userName}</if>
<if test="age != null">age = #{age}</if>
<if test="address != null and address != ''">address = #{add}</if>
where id = #{id}
</set>
</update>
删除数据 <delete>
对数据库中的一条或多条记录进行删除操作
删除单条记录
使用 <delete> 标签,并接收一个参数即可
<delete id="deleteByID">
delete
from xxx
where id = #{id}
</delete>
Mapper 接口中抽象方法
void deleteByID(int id);
批量删除记录
项目中会提供多选删除的功能,在数据库中,我们可以采用 in
关键字,同时删除多个id值的记录
删除多条记录需要把多个id值包装成一个int[]数组
Mapper接口中需要定义 int[] 数组
void deleteByIDs(int[] ids);
注意:对于Mybatis而言,传入的数组,会被转化为Map后再接收,而所接收的Value为该 int[] 数组,而Key则默认名的【array】;
<delete id="deleteByIDs">
delete from xxx where id in (?,?,?);
</delete>
对于一个数组而言,在Mapper.xml中需要通过遍历的填充【?】
<forEach> 标签
Mybatis 提供一种遍历方法 <forEach> 标签
<foreach collection="" item="" separator="," open="" close="" index=""></foreach>
collection
: 指接收到的数组,Mybatis 默认会把接收到的数组转化为Map集合,Key名为array
,所以在collection
中应接收“array”
item
: 循环遍历中每一个成员的值变量
separator
: 分割符,可选值,提供拼接sql时每个成员之间的分隔
open
: 开始符号,可选值,提供拼接sql前的开头符号
close
: 结束符号,可选值,提供拼接完成后的结束符号
index
: 循环下标值
// 使用 foreach 对多个 id 进行遍历,并拼接 (,) 等符号
<delete id="deleteByIDs">
delete from xxx where id in
<foreach collection="array" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
如果不希望使用默认名称,可在抽象方法中加入 @param()
进行定义
Mapper.java =>
void deleteByIDs(@Param("ids") int[] ids);
Mapper.xml =>
<delete id="deleteByIDs">
delete from xxx where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
注解方式查询sql
Mybatis 除了使用Mapper代理的方式,通过在xml文件中配置sql语句外,也提供了Java注解方式进行配置sql语句。
@select("select * from user where id = #{id}")
User selectByID(int id);
@update("update user set age = #{age},username=#{username} where id = #{id}")
int update(Map user)
@delete("delete user where id = #{id}")
void delete(int id)
@insert("insert into user() values()")
int insert(User user)
对于使用注解查询sql而言,在一些简单的sql语句下是非常有效方便的,但在复杂的sql语句中,需要使用xml.
在项目中,是否统一使用xml配置sql取决于团队要求或个人爱好而决定。
Mybatis 配置文件说明
首先配置文件的不同标签遵循以下顺序编写,如果顺序不对会报错
configuration(配置)
-> properties(属性)
-> settings(设置)
-> typeAliases(类型别名)
-> typeHandlers(类型处理器)
-> objectFactory(对象工厂)
-> plugins(插件)
-> environments(环境配置)
-> environment(环境变量)
-> transactionManager(事务管理器)
-> dataSource(数据源)
-> databaseIdProvider(数据库厂商标识)
-> mappers(映射器)
environments
数据源环境配置,其中包含两个标签一个属性
Mybatis 考虑到系统在开发、测试、生产等多种环节上,都需要使用数据库,而每一个阶段所使用的数据库也是可能不一样的,所以Mybatis支持配置多个数据源。
default : 当前正在使用的数据源
<environment id="name"> : 配置数据源标签
<transactionManager> : 数据源管理标签
type
: 设置事务管理的方式
type: [JDBC | MANAGER]
JDBC => 表示使用JDBC中原生的事务管理方式,如是否默认需要commit 等管理
MANAGER => 被管理,就是有第三方去帮助管理事务
<dataSource> : 设置数据源标签
type : 设置数据源的类型
POOLED => 表示使用数据库连接池
UNPOOLED => 表示不使用数据库连接池,每一次连接SQL都会重新连接数据库
JNDI => 表示使用上下文中的数据源。
properties(属性)
properties 是指某些数据值的定义,比如数据源中定义的 url 、usermane、password 处就使用了 property 属性,我们可以在 xml 文件中的外部定义属性值,再在其它标签中使用 ${propName} 的方式就能获取到。
// 可以引入外部属性值,也可以在内部定义 property 标签
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
// xml 中使用时,使用 ${} 符号引用就可以了
<dataSource type="POOLED">
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
property 默认值
从 MyBatis 3.4.2 开始,你可以为占位符指定一个默认值
<dataSource type="POOLED">
<!-- ... -->
<!-- 如果属性 'username' 没有被配置,'username' 属性的值将为 'ut_user' -->
<property name="username" value="${username:ut_user}"/>
</dataSource>
这个特性默认是关闭的。要启用这个特性,需要添加一个特定的属性来开启这个特性。
<properties resource="org/mybatis/example/config.properties">
<!-- ... -->
<!-- 启用默认值特性 -->
<property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/>
</properties>
因为 ${}
是可以使用表达式的,这难免有可能误使用了:
这个符号,比如三元表达式
${tableName != null ? tableName : 'global_constants'}
那么就需要更改默认值的分隔符
<properties resource="org/mybatis/example/config.properties">
<!-- ... -->
<!-- 修改默认值的分隔符 -->
<property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="?:"/>
</properties>
在使用的过程中就可以用自定义分隔符作为默认值分隔符了。
<dataSource type="POOLED">
<!-- ... -->
<property name="username" value="${db:username?:ut_user}"/>
</dataSource>
settings(设置)
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true | false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 | true | false | false |
aggressiveLazyLoading | 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。 | true | false | false (在 3.4.1 及之前的版本中默认为 true) |
multipleResultSetsEnabled | 是否允许单个语句返回多结果集(需要数据库驱动支持)。 | true | false | true |
useColumnLabel | 使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察。 | true | false | true |
useGeneratedKeys | 允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。 | true | false | False |
autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。 | NONE, PARTIAL, FULL | PARTIAL |
autoMappingUnknownColumnBehavior | 指定发现自动映射目标未知列(或未知属性类型)的行为。
|
NONE, WARNING, FAILING | NONE |
defaultExecutorType | 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement); BATCH 执行器不仅重用语句还会执行批量更新。 | SIMPLE REUSE BATCH | SIMPLE |
defaultStatementTimeout | 设置超时时间,它决定数据库驱动等待数据库响应的秒数。 | 任意正整数 | 未设置 (null) |
defaultFetchSize | 为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。 | 任意正整数 | 未设置 (null) |
defaultResultSetType | 指定语句默认的滚动策略。(新增于 3.5.2) | FORWARD_ONLY | SCROLL_SENSITIVE | SCROLL_INSENSITIVE | DEFAULT(等同于未设置) | 未设置 (null) |
safeRowBoundsEnabled | 是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。 | true | false | False |
safeResultHandlerEnabled | 是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false。 | true | false | True |
mapUnderscoreToCamelCase | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 | true | false | False |
localCacheScope | MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。 | SESSION | STATEMENT | SESSION |
jdbcTypeForNull | 当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。 某些数据库驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 | JdbcType 常量,常用值:NULL、VARCHAR 或 OTHER。 | OTHER |
lazyLoadTriggerMethods | 指定对象的哪些方法触发一次延迟加载。 | 用逗号分隔的方法列表。 | equals,clone,hashCode,toString |
defaultScriptingLanguage | 指定动态 SQL 生成使用的默认脚本语言。 | 一个类型别名或全限定类名。 | org.apache.ibatis.scripting.xmltags.XMLLanguageDriver |
defaultEnumTypeHandler | 指定 Enum 使用的默认 TypeHandler 。(新增于 3.4.5) | 一个类型别名或全限定类名。 | org.apache.ibatis.type.EnumTypeHandler |
callSettersOnNulls | 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值进行初始化时比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。 | true | false | false |
returnInstanceForEmptyRow | 当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于 3.4.2) | true | false | false |
logPrefix | 指定 MyBatis 增加到日志名称的前缀。 | 任何字符串 | 未设置 |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置 |
proxyFactory | 指定 Mybatis 创建可延迟加载对象所用到的代理工具。 | CGLIB | JAVASSIST | JAVASSIST (MyBatis 3.3 以上) |
vfsImpl | 指定 VFS 的实现 | 自定义 VFS 的实现的类全限定名,以逗号分隔。 | 未设置 |
useActualParamName | 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1) | true | false | true |
configurationFactory | 指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration() 的方法。(新增于 3.2.3) | 一个类型别名或完全限定类名。 |
一个配置完整的 settings 元素的示例如下:
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
typeAliases(类型别名)
我们发现,在定义 mapper 时,经常需要在 namespace 和 resultType 处声明类的全域名称,这样感觉非常麻烦,但是我们可以通过设置typeAliases来一次性指定我们的mapper接口存放在什么位置,这样我们在 namespace 和 resultType 处声明时,只需要填写类名就可以,并且不分大小写。
<typeAliases>
声明我们的接口在这个位置
<package name="cn.unsoft.Mybatis.Mapper"/>
</typeAliases>
同时我们也可以使用单个别名定义
<typeAliases>
通过单个声明别名,可以对某个Mapper接口类型设置一个别名
<typeAlias type="cn.unsoft.mybatis.mapper.UserMapper" alias="User"></typeAlias>
</typeAliases>
type 是指设置需要起别名的类型
alias 设置某个类型的别名,可以不设置,如果不设置,则自动为类名(不分大小写)
我们在mapper定义中,就可以只填写接口名就可以了,且不分大小写。
<mapper namespace="cn.unsoft.Mybatis.Mapper.UserMapper">
<select id="selectAll" resultType="User 或 user 都可以">
...
</select>
</mapper>
共有 0 条评论