文章目录
4. XML - 映射文件
MyBatis 的真正强大在于它的语句映射,MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。
SQL 映射文件只有很少的几个顶级元素(只列出常用元素):
select
– 映射查询语句。insert
– 映射插入语句。update
– 映射更新语句。delete
– 映射删除语句。sql
– 可被其它语句引用的可重用语句块。resultMap
– 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
4.1. select
查询语句是 MyBatis 中最常用的元素之一,能把数据存到数据库中价值并不大,还要能重新取出来才有用,多数应用也都是查询比修改要频繁。前面我们写了一个比较简单的select
<select id="selectOne" parameterType="int" resultType="com.czxy.mybatis.model.User">
SELECT
uid,
username,
birthday,
phone,
sex,
address
FROM `user`
WHERE uid = #{aa}
</select>
这个语句名为selectOne
,接受一个 int(或 Integer)类型的参数,并返回一个 User类型的对象.
@Test
public void selectOne()throws IOException {
//配置文件位置
String resource = "mybatis-config.xml";
//读取配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
//通过配置文件创建SessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
//可以通过 SqlSession 实例来直接执行已映射的 SQL 语句
//读取数据库连接
SqlSession sqlSession = sqlSessionFactory.openSession();
//通过 namespace.id的方式确定要执行的SQL片段
User user = sqlSession.selectOne("test.selectOne",12);
System.out.println(user);
//用完后必须管理连接
sqlSession.close();
}
4.1.1. 参数符号
注意参数符号:#{aa}
这就告诉 MyBatis 创建一个预处理语句(PreparedStatement)参数,在 JDBC 中,这样的一个参数在 SQL 中会由一个“?”来标识,
这样做更安全,更迅速,通常也是首选做法,因为参数会被双引号 “” 包裹,避免SQL注入,上述语句对应的SQL:
#<!-- #{id}表示占位符:获取传递的参数 ${id}原封不动的拼接-->
SELECT
uid,
username,
birthday,
phone,
sex,
address
FROM `user`
WHERE uid = "10"
参数符号还有另外一种方式: ${id}
有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串。 比如 ORDER BY 子句,这时候你可以:
ORDER BY ${columnName}
-- mapper
SELECT
uid,
username,
birthday,
phone,
sex,
address
FROM `user`
ORDER BY ${columnName} DESC
-- 对应的SQL
SELECT
uid,
username,
birthday,
phone,
sex,
address
FROM `user`
ORDER BY uid DESC -- 这里的列名就没有被双引号包裹
4.1.2. 参数类型
parameterType 用于明确参数类型,可以指定这条语句的参数类的全限定名或别名。
这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)
- 参数只有一个
- int / java.lang.Integer
- string
- float
- double
- date
- …
- 参数有多个
- parameterType=“map” / java.util.HashMap
- user / com.czxy.mybatis.model.User
- 使用map和pojo类型传参
UserMapper.xml
<!--参数是map类型 需求:根据手机号或者性别进行查询-->
<select id="listUser" parameterType="map" resultType="user">
SELECT
uid,
username,
birthday,
phone,
sex,
address
FROM `user`
WHERE sex = #{sex}
AND phone like #{phone}
</select>
MyBatisTest类
@Test
public void listUser() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过sqlSession获取与数据库的会话连接,完成各种SQL的执行
SqlSession sqlSession = sqlSessionFactory.openSession();
//准备查询参数
//1. pojo类型
//User user = new User();
//user.setPhone("%"+888+"%");
//user.setSex("1");
//2.map类型参数,注意key必须和占位符名称一致#{key},否则获取不到参数
HashMap<String, Object> map = new HashMap<>();
map.put("phone","%"+888+"%");
map.put("sex","1");
//通过 namespace.id的方式确定要执行的SQL片段
//List<User> list = sqlSession.selectList("test.listUser", user);
List<User> list = sqlSession.selectList("test.listUser", map);
for (User u : list) {
System.out.println(u);
}
//用完后必须管理连接
sqlSession.close();
}
4.1.3. 结果映射
resultType
- 返回结果的数据类型。通常 MyBatis 可以推断出来,但是为了更加准确,建议写上。
- 返回一条或者多条数据,结果类型不变
- 返回一条User类型数据和返回多条User类型数据,resultType结果都是resultType=“user”
- resultType和resultMap必须有一个属性被指定
<select id="selectUser" parameterType="int" resultType="map">
SELECT
uid,
username,
birthday,
phone,
sex,
address
FROM `user`
WHERE uid = #{uid}
</select>
返回结果:
{birthday=1998-10-16, uid=10, address=北京海淀, phone=13999999999, sex=1, username=张三}
- resultMap
当查询结果有多个列,并且列名和字段名称不一致时,mybatis无法将结果封装到model对象中,这是需要通过resultMap绑定列和属性关系
<!--resultMap结果映射:绑定列和属性关系-->
<resultMap id="baseResultMap" type="user">
<id property="uid" column="uid" javaType="int" jdbcType="INTEGER"></id>
<result property="username" column="username"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
<result property="phone" column="phone"></result>
<result property="address" column="address"></result>
</resultMap>
<!--参数是map类型, 返回结果类型是resultMap-->
<select id="listUser2" parameterType="map" resultMap="baseResultMap">
SELECT
uid,
username,
birthday,
phone,
sex,
address
FROM `user`
WHERE sex = #{sex}
AND phone like #{phone}
</select>
- resultMap和resultType的区别
https://blog.csdn.net/xiao_xiao3601/article/details/92724587?utm_source=app
测试:MyBatisTest.java
@Test
public void listUser2() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过sqlSession获取与数据库的会话连接,完成各种SQL的执行
SqlSession sqlSession = sqlSessionFactory.openSession();
//map类型参数,注意key必须和占位符名称一致#{key},否则获取不到参数
HashMap<String, Object> map = new HashMap<>();
map.put("phone","%"+6+"%");
map.put("sex","1");
//通过 namespace.id的方式确定要执行的SQL片段
List<User> list = sqlSession.selectList("test.listUser2", map);
for (User u : list) {
System.out.println(u);
}
//用完后必须管理连接
sqlSession.close();
}
附件 - 支持的 JDBC 类型
为了以后可能的使用场景,MyBatis 通过内置的 jdbcType 枚举类型支持下面的 JDBC 类型。
BIT |
FLOAT |
CHAR |
TIMESTAMP |
OTHER |
UNDEFINED |
---|---|---|---|---|---|
TINYINT |
REAL |
VARCHAR |
BINARY |
BLOB |
NVARCHAR |
SMALLINT |
DOUBLE |
LONGVARCHAR |
VARBINARY |
CLOB |
NCHAR |
INTEGER |
NUMERIC |
DATE |
LONGVARBINARY |
BOOLEAN |
NCLOB |
BIGINT |
DECIMAL |
TIME |
NULL |
CURSOR |
ARRAY |
- 内容回顾
-
mybatis 入门案例
- 导入maven依赖
- 添加了配置文件
- jdbc.properties : 配置了数据库的连接
- mybatis-config.xml : 整合jdbc.properties / UserMapper.xml ,这样mybatis是不是清楚了数据源在哪,清楚要执行的具体SQL有哪些
- UserMapper.xml : SQL片段(statement)
- 测试程序:读取了核心配置文件mybatis-config.xml,创建了sqlSessionFactory,通过sqlSessionFactory可以创建sqlSession(会话连接)连接到数据库,可以使用sqlSession执行SQL片段
-
入门程序中一些细节
- mybatis-config.xml
- UserMapper.xml
- 主要编写SQL片段
- 参数
- 指定接收参数类型:resultType=“别名”,默认可以不写,mybatis会自动推导
- 使用接收参数:#{} ${} ,
- #{}使用占位符 ? ,参数被“”包裹,好处-安全
- ${}: 直接把参数拼接到SQL后面,在 group by uid
- 返回值类型
- map
- 使用pojo对象接收:类的属性和表的字段名称要么一模一样,要么符合驼峰命名规则
- 属性名: userName 对应字段 user_name
- 特殊情况:不符第二种规则,需要手动绑定字段和属性的关系
- 使用resultMap 手动绑定column和属性之间的关系
4.2. insert (插入数据)
- UserMapper.xml
<!--parameterType可以省略,mybatis会自动推导参数类型-->
<insert id="insertUser">
INSERT INTO `user`
(uid,username,birthday,phone,sex,address)
VALUES(NULL,#{username},#{birthday},#{phone},#{sex},#{address})
</insert>
- 测试:MyBatisTest.java
//插入数据
@Test
public void testInsert() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过sqlSession获取与数据库的会话连接,完成各种SQL的执行
SqlSession sqlSession = sqlSessionFactory.openSession();
//准备数据
User user= new User();
user.setUsername("曹操");
user.setBirthday(new Date());
user.setAddress("安徽亳州");
user.setPhone("13666666666");
user.setSex("1");
//通过 namespace.id的方式确定要执行的SQL片段
sqlSession.insert("test.insertUser",user);
//提交事务
sqlSession.commit();
//用完后必须管理连接
sqlSession.close();
}
4.2.1. 返回生成的主键ID
在插入语句里面有一些额外的属性和子元素用来处理主键的生成,如果你的数据库支持自动生成主键的字段,那么可以进行如下设置 :
- useGeneratedKeys=”true”,
- keyProperty =“目标属性”
这样做的好处是可以返回自动生成的主键。
- 应用场景:商品相关操作的日志记录
管理员新上架商品:张三(10)上架华为P40 Pro(1001),对应日志如下:
user表已经在 uid 列上使用了自增主键,那么语句可以修改为:
- UserMapper.xml
<!--1. 先观确定主键ID是否是自增的-->
<!--2. 修改成能返回主键ID-->
<!--插入user数据,并且返回主键ID-->
<!-- - useGeneratedKeys=”true” 使用生成的主键,默认是false,不适用生成的主键(不返回生成的主键),-->
<!-- - keyProperty ="目标属性" 把主键返回到哪个属性中-->
<insert id="insertUserAndReturnID" useGeneratedKeys="true" keyProperty="uid">
INSERT INTO `user`
(uid,username,birthday,phone,sex,address)
VALUES(NULL,#{username},#{birthday},#{phone},#{sex},#{address})
</insert>
- 测试:MyBatisTest.java
@Test
public void testInsert() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过sqlSession获取与数据库的会话连接,完成各种SQL的执行
SqlSession sqlSession = sqlSessionFactory.openSession();
//准备数据
User user= new User();
user.setUsername("曹操");
user.setBirthday(new Date());
user.setAddress("安徽亳州");
user.setPhone("13666666666");
user.setSex("1");
//通过 namespace.id的方式确定要执行的SQL片段
//sqlSession.insert("test.insertUser",user);
sqlSession.insert("test.insertUserAndReturnID",user);
System.out.println("---------- " +user.getUid()+" -----------");
//提交事务
sqlSession.commit();
//用完后必须管理连接
sqlSession.close();
}
4.3. update(更新数据)
UserMapper.xml
<update id="updateUser" >
UPDATE `user`
SET phone = #{phone}
WHERE uid = #{uid}
</update>
- 测试:MyBatisTest.java
@Test
public void updateUser() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过sqlSession获取与数据库的会话连接,完成各种SQL的执行
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setUid(32);
user.setPhone("13612345678");
//通过 namespace.id的方式确定要执行的SQL片段
sqlSession.update("test.updateUser",user);
//提交事务
sqlSession.commit();
//用完后必须管理连接
sqlSession.close();
}
4.4. delete(删除数据)
- UserMapper.xml
<delete id="deleteUser" >
DELETE FROM `user` WHERE uid = #{uid}
</delete>
- 测试:MyBatisTest.java
@Test
public void deleteUser() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过sqlSession获取与数据库的会话连接,完成各种SQL的执行
SqlSession sqlSession = sqlSessionFactory.openSession();
//使用map传递参数,KEY必须和占位符#{key}名称一致,否则获取不到参数
HashMap<String, Integer> map = new HashMap<>();
map.put("uid",2);
//通过 namespace.id的方式确定要执行的SQL片段
sqlSession.delete("test.deleteUser",map);
//提交事务
sqlSession.commit();
//用完后必须管理连接
sqlSession.close();
}
4.5. SQL
- SQL元素的作用
前面我们在写SQL的时候可以发现,出现大量重复的SQL片段,例如查询的时候,列名在个SQL都有书写
SELECT
uid,
username,
birthday,
phone,
sex,
address
FROM `user`
WHERE uid = #{aa}
- 使用SQL元素
- 使用SQL元素定义SQL片段
- 使用
<include refid="片段ID"></include>
引用
- 示例:这里用SQL元素来定义可重用的 SQL 代码片段,以便在其它语句中使用。
<sql id="baseColunm">
uid,username,birthday,phone,sex,address
</sql>
<!--参数是map类型, 返回结果类型是resultMap-->
<select id="listUser2" parameterType="map" resultMap="baseResultMap">
SELECT
<include refid="baseColunm"></include>
FROM `user`
WHERE sex = #{sex}
AND phone like #{phone}
</select>
4.6. 添工具类
创建sqlSessionFactory和获取sqlSession的方式是固定的,sqlSessionFactory只需要创建一次即可,因此使用工具类MyBatisUtils来封装相关操作,简化书写,后续SSM整合之后,这些对象的创建就交给spring容器管理了,不需要我们自己管理了。
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory;
/*
* 创建本地线程变量,为每一个线程独立管理一个session对象 每一个线程只有且仅有单独且唯一的一个session对象
* 使用ThreadLocal对session进行管理,可以保证线程安全,避免多实例同时调用同一个session对象
*/
private static ThreadLocal<SqlSession> threadlocal = new ThreadLocal<SqlSession>();
// 创建sessionFactory对象,因为整个应用程序只需要一个实例对象,故用静态代码块
static {
try {
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 新建session会话,并把session放在线程变量中
*/
private static void newSession() {
// 打开一个session会话
SqlSession session = sqlSessionFactory.openSession();
SqlSession sqlSession = threadlocal.get();
// 将session会话保存在本线程变量中
threadlocal.set(session);
}
/**
* 返回session对象
* @return session
*/
public static SqlSession getSession(){
//优先从线程变量中取session对象
SqlSession session = threadlocal.get();
//如果线程变量中的session为null,
if(session==null){
//新建session会话,并把session放在线程变量中
newSession();
//再次从线程变量中取session对象
session = threadlocal.get();
}
return session;
}
/**
* 关闭session对象,并从线程变量中删除
*/
public static void closeSession(){
//读取出线程变量中session对象
SqlSession session = threadlocal.get();
//如果session对象不为空,关闭sessoin对象,并清空线程变量
if(session!=null){
//关闭资源
session.close();
//从threadlocal中移除session
threadlocal.set(null);
}
}
/**
* 提交并关闭资源
*/
public static void commitAndclose() {
//获取连接
SqlSession openSession = getSession();
//非空判断
if(openSession!=null) {
//提交
openSession.commit();
//关闭资源
openSession.close();
//从threadlocal中移除session
threadlocal.remove();
}
}
}