文章目录
什么是Mybatis
- MyBatis 是一款优秀的持久层框架。
- 它支持自定义 SQL、存储过程以及高级映射。
- MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
- MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
获取方式
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>x.x.x</version>
</dependency>
传统的JDBC
传统jdbc的步骤
package com.meimeixia.mybatis.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JdbcTest {
//1. 加载驱动
//2. 创建连接
//3. 设置SQL语句
//4. 创建一个Statement对象
//5. 设置参数
//6. 执行查询,得到一个ResultSet对象
//7. 遍历结果集,输出结果
//8. 释放资源
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
// 加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 通过驱动管理类获取数据库链接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "liayun");
// 定义sql语句 ?表示占位符
String sql = "select * from user where username = ?";
// 获取预处理statement
preparedStatement = connection.prepareStatement(sql);
// 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
preparedStatement.setString(1, "王五");
// 向数据库发出sql执行查询,查询出结果集
resultSet = preparedStatement.executeQuery();
// 遍历查询结果集
while (resultSet.next()) {
System.out.println(resultSet.getString("id")+" "+ resultSet.getString("username"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
传统jdbc带来的不便
-
数据库连接的创建、释放频繁造成资源浪费从而影响系统性能,如果使用数据库连接池可以解决这个问题。
-
代码不易维护,失去了一旦变动,据需要修改java代码
String sql = "select * from user where id = ?"; preparedStatement = connection.prepareStatement(sql);
-
使用
preparedStatement
向占位符传参存在硬编码,因为SQL语句的where条件不确定,可能多也可能少,修改SQL语句还要修改Java代码,导致系统不易维护。preparedStatement.setString(1,"mike");
-
对结果集解析存在硬编码(查询列名),SQL语句变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析则比较方便。
// 遍历查询结果集 while (resultSet.next()) { System.out.println(resultSet.getString("id")+" "+resultSet.getString("username")); }
第一个Mybatis程序
创建一个项目引入相关依赖
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
XML进行核心配置
XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器(TransactionManager)。
<?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">
<!--Mybatis的主配置文件-->
<configuration>
<!--动态一如database信息-->
<properties resource="jdbc.properties"/>
<!--设置单个别名-->
<typeAliases>
<!--
单个别名 在mapper中不区分大小写
<typeAlias type="domain.Student" alias="student" />
-->
<!--批量定义别名:以下会自动将该包中的所有类 批量定义别名 - 别名就是类名(忽略大小写)-->
<package name="domain"/>
</typeAliases>
<!--配置环境-->
<!--environments里面可以有多个不同的environment,通过default确定目前使用的数据库环境-->
<environments default="mysql">
<!--配置mysql环境-->
<environment id="mysql">
<!--配置事务类型-->
<!--事务提交方式:
JDBC:利用JDBC方式处理事务(commit rollback close)
MANAGE:将事务交由其他组件去托管(Spring JBoss)
默认情况下会关闭连接:若想默认不关闭则:<property name="closeConnection" value="false"/> -->
<transactionManager type="JDBC"></transactionManager>
<!--数据源类型:
UNPOOL:传统JDBC模式
POOLED:使用连接池
JNDI:从tomcat中获取一个内置的数据库连接池
<!--配置数据源 也叫连接池-->
<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>
<!-- 指定映射配置文件位置,映射配置文件指的是每个dao独立的配置文件-->
<mappers>
<mapper resource="UserMapper.xml"></mapper>
</mappers>
<!-- 如果是使用注解来配置的话,此处应该使用class属性指定被注解的dao权限定类名-->
<!-- <mappers>-->
<!-- <mapper class="dao.UserDao"></mapper>-->
<!-- </mappers>-->
</configuration>
jdbc.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis
username=root
password=1996613nba
构建SqlSessionFactory
- 从 XML 文件中构建
SqlSessionFactory
的实例非常简单,建议使用类路径下的资源文件进行配置。 但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file:// URL 构造的输入流。MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。 - 每个基于 MyBatis 的应用都是以一个
SqlSessionFactory
的实例为核心的。SqlSessionFactory
的实例可以通过SqlSessionFactoryBuilder
获得。而SqlSessionFactoryBuilder
则可以从 XML 配置文件或一个预先配置的Configuration
实例来构建出SqlSessionFactory
实例。 - 既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
编写一个获取SqlSession
对象的工具类以方便获取对象
package com.yy.utils;
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;
/**
* @author YuanYong(1218639030@qq.com)
* @date 2020/3/23 0:30
* @description:TODO
*/
public class MybatisUtils {
/**
* 获取SqlSessionFactory对象
*/
private static SqlSessionFactory sqlSessionFactory;
static {
String resource = "mybatis-comfig.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
/**
* 返回SqlSession对象
*/
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
创建实体类
使用Lombok
package com.yy.pojo;
import lombok.Data;
import java.util.Date;
/**
* @author YuanYong(1218639030 @ qq.com)
* @date 2020/3/23 0:51
* @description:TODO
*/
@Data
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
private List<Order> orderList;
}
创建dao层接口和mapper
UserDao.java
package com.yy.mapper;
import com.yy.pojo.User;
import java.util.List;
/**
* @author YuanYong(1218639030 @ qq.com)
* @date 2020/3/23 1:00
* @description:TODO
*/
public interface UserMapper {
List<User> getUserList();
}
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">
<!--
命名空间的作用有两个:
1.一个是利用更长的全限定名来将不同的语句隔离开来
2.同时也实现了你上面见到的接口绑定
只要将命名空间置于合适的 Java 包命名空间之中,你的代码会变得更加整洁,也有利于你更方便地使用 MyBatis。
-->
<mapper namespace="com.yy.dao.UserMapper">
<!--虽然返回的值是List<User> Mybatis会根据实际情况将返回值判断是User或List<User>-->
<select id="getUserList" resultType="com.yy.pojo.User">
select * from user where 1 = 1
</select>
</mapper>
为了这个简单的例子,我们似乎写了不少配置,但其实并不多。在一个 XML 映射文件中,可以定义无数个映射语句,这样一来,XML 头部和文档类型声明部分就显得微不足道了。文档的其它部分很直白,容易理解。
将mapper.xml在核心配置文件中配置
<!--指定映射配置文件位置,映射配置文件指的是每个dao独立的配置文件-->
<mappers>
<mapper resource="com/yy/dao/UserMapper.xml"></mapper>
</mappers>
测试
import com.yy.dao.UserDao;
import com.yy.pojo.User;
import com.yy.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
/**
* @author YuanYong(1218639030 @ qq.com)
* @date 2020/3/23 1:17
* @description:TODO
*/
public class Mybatis {
@Test
public void test(){
//1.获取sqlSession连接
SqlSession session = MybatisUtils.getSqlSession();
//2.获取mapper
UserDao userDao = session.getMapper(UserDao.class);
List<User> list = userDao.getUserList();
//3.遍历结果集
for (User user:list) {
System.out.println(user);
}
//4.关闭连接
session.close();
}
}
测试结果
User(id=1, username=王五, birthday=null, sex=2, address=null)
User(id=10, username=张三, birthday=Thu Jul 10 00:00:00 CST 2014, sex=1, address=北京市)
User(id=16, username=张小明, birthday=null, sex=1, address=河南郑州)
User(id=22, username=陈小明, birthday=null, sex=1, address=河南郑州)
User(id=24, username=张三丰, birthday=null, sex=1, address=河南郑州)
User(id=25, username=陈小明, birthday=null, sex=1, address=河南郑州)
User(id=26, username=王五, birthday=null, sex=null, address=null)
User(id=29, username=苑勇, birthday=Sat Jan 18 00:00:00 CST 2020, sex=1, address=青岛)
CRUD
条件查询
-
创建接口
User selectUserById(Integer id);
-
在
mapper.xml
文件中写入查询语句<select id="selectUserById" parameterType="Integer" resultType="com.yy.pojo.User"> select * from user where id = #{id} </select>
-
测试
@Test public void selectUserById(){ //获取SqlSession连接 SqlSession session = MybatisUtils.getSqlSession(); //获取mapper对象 UserMapper mapper = session.getMapper(UserMapper.class); //执行方法 User user = mapper.selectUserById(1); //打印结果 System.out.println(user); //关闭连接 session.close(); }
-
结果
User(id=1, username=王五, birthday=null, sex=2, address=null)
插入
-
创建接口
Integer insertUser(User user);
-
在
mapper.xml
文件中写入查询语句<insert id="insertUser" parameterType="com.yy.pojo.User"> <selectKey keyProperty="id" resultType="Integer" order="AFTER"> SELECT LAST_INSERT_ID() </selectKey> insert into user(username, birthday, sex, address) values (#{username},#{birthday},#{sex},#{address}); </insert>
属性 描述 keyProperty
selectKey
语句结果应该被设置到的目标属性。如果生成列不止一个,可以用逗号分隔多个属性名称。keyColumn
返回结果集中生成列属性的列名。如果生成列不止一个,可以用逗号分隔多个属性名称。 resultType
结果的类型。通常 MyBatis 可以推断出来,但是为了更加准确,写上也不会有什么问题。MyBatis 允许将任何简单类型用作主键的类型,包括字符串。如果生成列不止一个,则可以使用包含期望属性的 Object 或 Map。 order
可以设置为 BEFORE
或AFTER
。如果设置为BEFORE
,那么它首先会生成主键,设置keyProperty
再执行插入语句。如果设置为AFTER
,那么先执行插入语句,然后是selectKey
中的语句 - 这和 Oracle 数据库的行为相似,在插入语句内部可能有嵌入索引调用。statementType
和前面一样,MyBatis 支持 STATEMENT
,PREPARED
和CALLABLE
类型的映射语句,分别代表Statement
,PreparedStatement
和CallableStatement
类型。 -
测试
@Test public void insertUser(){ User user = new User(); user.setUsername("CodeYuan-Y"); Date date = new Date(); user.setBirthday(date); user.setSex("男"); user.setAddress("青岛市"); //获取SqlSession连接 SqlSession session = MybatisUtils.getSqlSession(); //获取mapper对象 UserMapper mapper = session.getMapper(UserMapper.class); //执行sql mapper.insertUser(user); //提交事务 session.commit(); //输出自增id System.out.println("自增id为:"+user.getId()); //关闭连接 session.close(); }
-
结果
自增id为:35
修改
-
创建接口
Integer updateUser(User user);
-
Sql语句
<update id="updateUser" parameterType="com.yy.pojo.User"> update user set username = #{username} where id = #{id} </update>
-
测试
@Test public void updateUser(){ User user = new User(); user.setId(1); user.setUsername("CodeYuan-Y"); //获取SqlSession对象 SqlSession session = MybatisUtils.getSqlSession(); //获取mapper对象 UserMapper mapper = session.getMapper(UserMapper.class); //执行方法 mapper.updateUser(user); //提交事务 session.commit(); //关闭连接 session.close(); }
删除
换一种写法…
假设我们的实体类,或者数据库中的表、字段过多,我们的参数应当考虑Map
-
创建接口
Integer deleteUser(Map<String,Object> map);
-
在
mapper.xml·
中创建sql<delete id="deleteUser" parameterType="map"> delete from user where id = #{userid} and username = #{username} </delete>
-
测试
@Test public void deleteUser(){ Map<String,Object> map = new HashMap<String, Object>(); map.put("userid",29); map.put("username","苑勇"); //获取SqlSession对象 SqlSession session = MybatisUtils.getSqlSession(); //获取mapper对象 UserMapper mapper = session.getMapper(UserMapper.class); //调用方法 mapper.deleteUser(map); //提交事务 session.commit(); //关闭连接 session.close(); }
模糊查询
创建接口
List<User> selectUserByLike(String s);
-
java代码中传递通配符。
-
mapper.xml
配置<select id="selectUserByLike" parameterType="java.lang.String" resultType="com.yy.pojo.User"> select * from user where username like #{value} </select>
-
测试
@Test public void selectUserByLike(){ //获取SqlSession对象 SqlSession session = MybatisUtils.getSqlSession(); //获取mapper UserMapper mapper = session.getMapper(UserMapper.class); //传入参数 List<User> list = mapper.selectUserByLike("%张%"); //提交事务 session.commit(); //遍历结果集 for (User user:list) { System.out.println(user); } //关闭连接 }
-
结果
User(id=10, username=张三, birthday=Thu Jul 10 00:00:00 CST 2014, sex=1, address=北京市) User(id=16, username=张小明, birthday=null, sex=1, address=河南郑州) User(id=24, username=张三丰, birthday=null, sex=1, address=河南郑州)
-
-
sql中拼接通配符(容易sql注入)
-
mapper.xml
配置<select id="selectUserByLike" parameterType="java.lang.String" resultType="com.yy.pojo.User"> select * from user where username like "%"#{value}"%" </select>
-
测试
@Test public void selectUserByLike(){ //获取SqlSession对象 SqlSession session = MybatisUtils.getSqlSession(); //获取mapper UserMapper mapper = session.getMapper(UserMapper.class); //传入参数 List<User> list = mapper.selectUserByLike("张"); //提交事务 session.commit(); //遍历结果集 for (User user:list) { System.out.println(user); } //关闭连接 session.close(); }
-
结果
User(id=10, username=张三, birthday=Thu Jul 10 00:00:00 CST 2014, sex=1, address=北京市) User(id=16, username=张小明, birthday=null, sex=1, address=河南郑州) User(id=24, username=张三丰, birthday=null, sex=1, address=河南郑州)
-
&{}与#{}的应用
默认情况下,使用 #{}
参数语法时,MyBatis 会创建 PreparedStatement
参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。 这样做更安全,更迅速,通常也是首选做法,不过有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串。 比如 ORDER BY 子句,这时候你可以:
ORDER BY ${columnName}
这样,MyBatis 就不会修改或转义该字符串了。
核心文件配置
properties属性
在mybatis-config.xml
文件中,可把数据库连接信息配置到properties标签当中。
<!--动态一如database信息-->
<properties resource="jdbc.properties"/>
<!--配置数据源 也叫连接池-->
<dataSource type="POOLED">
<!--配置数据库的基本信息-->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
MyBatis将按照下面的顺序来加载属性:
- 在properties元素体内定义的属性首先被读取;
- 然后会读取properties元素中resource或url加载的属性,它会覆盖已读取的同名属性。
说得通俗一点就是:先加载property元素内部的属性,然后再加载jdbc.properties文件外部的属性,如果有同名属性则会覆盖。可以将properties元素内部jdbc.username属性的值故意写错,即故意将数据库连接的用户名给整错,结果发现仍然好使,这足以说明问题了。
typeAliases(类型别名)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书 。
要注意的是:别名不区分大小写 UsEr 和 user效果是一样的
<!--别名配置-->
<typeAliases>
<typeAlias type="com.yy.pojo.User" alias="user" />
</typeAliases>
配置别名后,mapper里面的一些参数类型和返回类型就可以使用别名
原来的写法
<update id="updateUser" parameterType="com.yy.pojo.User">
update user set username = #{username} where id = #{id}
</update>
现在的写法
<update id="updateUser" parameterType="user">
update user set username = #{username} where id = #{id}
</update>
如果像这样为每一个pojo定义一个别名,那不是傻逼吗!万一一个项目里面有很多pojo类呢?所以我们可以批量定义别名,就像下面这样。
<typeAliases>
<package name="com.yy.pojo"/>
</typeAliases>
Mapper映射配置
Mapper(映射器)的配置有如下三种方式:
-
第一种方式:使用
<mapper resource="mapper/UserMapper.xml" />
标签来加载相对于类路径的映射文件 -
第二种方式:使用
<mapper class="com.yy.mybatis.mapper.UserMapper" />
标签配置映射文件的class扫描器,也就是说加载Mapper接口类路径下的映射文件。注意:这种方式要求Mapper接口名称和Mapper映射文件名称相同,且放在同一个目录中。
-
第三种方式:使用
<package name="com.yy.mybatis.mapper" />
标签配置映射文件包扫描,注册指定包下的所有Mapper接口,也就是说能加载指定包下的所有映射文件了。注意:这种方式也要求Mapper接口名称和Mapper映射文件名称相同,且放在同一个目录中。
虽然Mapper(映射器)配置有以上三种方式,但是在实际开发中就用第三种方法,其他方式仅做了解就行!
第三种实例
<!--指定映射配置文件夹位置-->
<mappers>
<!--<mapper resource="mapper/UserMapper.xml"></mapper>-->
<!--<mapper class="dao.UserDao"></mapper>-->
<package name="com.yy.mybatis.mapper"/>
</mappers>
第三种方式IDEA maven项目默认不会把src下除java文件外的文件打包到classes文件夹下,需要在pom.xml中增加配置
src/main/java **/*.xml日志打印配置
配置日志打印可以更好的帮助我们查看过程,发现错误,以下是STDOUT_LOGGING
配置
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
输出日志实例
Created connection 33105141.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f924f5]
==> Preparing: select * from user where username like "%"?"%"
==> Parameters: 张(String)
<== Columns: id, username, birthday, sex, address
<== Row: 10, 张三, 2014-07-10, 1, 北京市
<== Row: 16, 张小明, null, 1, 河南郑州
<== Row: 24, 张三丰, null, 1, 河南郑州
<== Total: 3
ResultMap结果集映射
结果映射(resultMap)
resultType
虽然可以指定将查询结果映射为pojo,但需要pojo的属性名和SQL语句查询的列名一致方可映射成功。如果SQL语句查询字段名和pojo的属性名不一致,那么可以通过resultMap
将字段名和属性名作一个对应关系,resultMap
实质上还是需要将查询结果映射到pojo对象中。resultMap
可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和List
集合就能分别实现一对一查询和一对多查询。
-
创建接口
List<User> getUserListMap();
-
配置映射
<!-- resultMap就是映射数据库和pojo之间关系的 type:映射pojo上,可以使用别名 id:唯一标识相当于sql语句的id --> <resultMap id="user_list_map" type="user"> <!--id标签用于映射主键,property属性是pojo类中主键属性,column是返回结果中的主键列--> <id property="id" column="id"/> <!--以下是普通属性,不配置下列属性也可以,但是如果设计到多表时有可能需要写全,还是写全为妙--> <result property="username" column="username"/> <result property="sex" column="sex"/> <result property="address" column="address"/> </resultMap> <select id="getUserListMap" resultMap="user_list_map"> select id,username,sex,address from user </select>
-
测试
@Test public void getUserListMap(){ //获取SqlSession对象 SqlSession session = MybatisUtils.getSqlSession(); //获取mapper UserMapper mapper = session.getMapper(UserMapper.class); //调用方法 List<User> userListMap = mapper.getUserListMap(); //提交事务 session.commit(); //输出结果集 for (User u:userListMap) { System.out.println(u); } //关闭连接 session.close(); }
-
结果
User(id=1, username=CodeYuan-Y, birthday=null, sex=2, address=null) User(id=10, username=张三, birthday=null, sex=1, address=北京市) User(id=16, username=张小明, birthday=null, sex=1, address=河南郑州) User(id=22, username=陈小明, birthday=null, sex=1, address=河南郑州) User(id=24, username=张三丰, birthday=null, sex=1, address=河南郑州) User(id=25, username=陈小明, birthday=null, sex=1, address=河南郑州) User(id=26, username=王五, birthday=null, sex=null, address=null) User(id=35, username=CodeYuan-Y, birthday=null, sex=男, address=青岛市)
-
constructor
- 用于在实例化类时,注入结果到构造方法中
idArg
- ID 参数;标记出作为 ID 的结果可以帮助提高整体性能arg
- 将被注入到构造方法的一个普通结果
-
id
– 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能 -
result
– 注入到字段或 JavaBean 属性的普通结果 -
association
一个复杂类型的关联;许多结果将包装成这种类型
- 嵌套结果映射 – 关联可以是
resultMap
元素,或是对其它结果映射的引用
- 嵌套结果映射 – 关联可以是
-
collection
一个复杂类型的集合
- 嵌套结果映射 – 集合可以是
resultMap
元素,或是对其它结果映射的引用
- 嵌套结果映射 – 集合可以是
属性 | 描述 |
---|---|
property | 映射到列结果的字段或属性。如果 JavaBean 有这个名字的属性(property),会先使用该属性。否则 MyBatis 将会寻找给定名称的字段(field)。 无论是哪一种情形,你都可以使用常见的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。 |
column | 数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。 |
javaType | 一个 Java 类的全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。 |
一对一查询
场景:一个订单中 订单信息 + User信息
-
创建订单实体类
@Data public class Order { private Integer id; private String number; private Date createtime; private String note; private User user; }
-
创建接口
public interface OrderMapper { Order selectOrderById(Integer id); }
-
mapper.xml
配置<resultMap id="orderMap" type="order"> <id property="id" column="id"/> <result property="number" column="number"/> <result property="createtime" column="createtime" /> <result property="note" column="note"/> <!-- association:配置的一对一属性 --> <!-- property:名字 javaType:类型 --> <association property="user" javaType="user"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="sex" column="sex"/> <result property="address" column="address"/> </association> </resultMap> <select id="selectOrderById" parameterType="int" resultMap="orderMap"> select o.id,o.number,o.createtime,o.note, u.id,u.username,u.sex,u.address from mybatis.order as o join user as u on user_id = u.id where o.id = #{id} </select>
-
测试
@Test public void selectOrderById(){ //获取SqlSession对象 SqlSession session = MybatisUtils.getSqlSession(); //获取mapper OrderMapper mapper = session.getMapper(OrderMapper.class); //调用方法 Order order = mapper.selectOrderById(3); //提交事务 session.commit(); //输出结果集 System.out.println(order); //关闭连接 session.close(); }
-
结果
<== Columns: id, number, createtime, note, id, username, sex, address <== Row: 3, 1000010, 2015-02-04 13:22:35.0, null, 1, CodeYuan-Y, 2, null <== Total:
一对多查询
场景 查询一个用户所有的订单信息
-
接口
List<User> selectOrderByUserId(Integer id);
-
mapper.xml
配置<resultMap id="userMap" type="user"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="sex" column="sex"/> <result property="address" column="address"/> <!-- property同association中的一样是属性名称(javaBean中的); javaType也同association中的是类型, 最后多了一个OfType,因为一对多,不像一对一是单个的!我们这里是List集合 一对多其中是放的一个集合所以这个是集合的泛型的类型,这里我们的list中放的是Teacher: 所以这里是Order。 --> <collection property="orderList" javaType="list" ofType="order"> <id property="id" column="id"/> <result property="number" column="number"/> <result property="note" column="note"/> </collection> </resultMap> <select id="selectOrderByUserId" parameterType="Integer" resultMap="userMap"> select u.id,u.username,u.sex,u.address, o.id,o.number,o.note from mybatis.order as o,user as u where o.user_id = u.id and u.id = #{id} </select>
-
测试
@Test public void selectOrderByUserId(){ //获取SqlSession对象 SqlSession session = MybatisUtils.getSqlSession(); //获取mapper OrderMapper mapper = session.getMapper(OrderMapper.class); //调用方法 List<User> list = mapper.selectOrderByUserId(1); //提交事务 session.commit(); //输出结果集 System.out.println(list); //关闭连接 session.close(); }
-
结果
==> Preparing: select u.id,u.username,u.sex,u.address, o.id,o.number,o.note from mybatis.order as o,user as u where o.user_id = u.id and u.id = ? ==> Parameters: 1(Integer) <== Columns: id, username, sex, address, id, number, note <== Row: 1, CodeYuan-Y, 2, null, 3, 1000010, 铅笔 <== Row: 1, CodeYuan-Y, 2, null, 4, 1000011, 钢笔 <== Total: 2
多对多查询
多对多、多对多对多…只要你想对下去一定可以,它的实现原理主要是collection
的嵌套又嵌套
动态SQL开发
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
IF查询
-
场景:查询时,如果输入了某些内容就模糊查询,如果没输入就查询全部
-
接口
List<User> selectUserList(String str);
-
sql语句
<select id="selectUserList" parameterType="string" resultType="user"> select * from user where 1 = 1 <if test="username != null"> and username like #{username} </if> </select> ------------------或------------------ <select id="selectUserList" parameterType="string" resultType="user"> select * from user <where> <if test="username != null"> and username like #{username} </if> </where> </select>
-
测试
@Test public void selectUserList(){ //获取SqlSession对象 SqlSession session = MybatisUtils.getSqlSession(); //获取mapper UserMapper mapper = session.getMapper(UserMapper.class); //调用方法 List<User> list = mapper.selectUserList("%张%"); //提交事务 session.commit(); //输出结果集 System.out.println(list); //关闭连接 session.close(); }
-
结果
==> Preparing: select * from user where 1 = 1 and username like ? ==> Parameters: %张%(String) <== Columns: id, username, birthday, sex, address <== Row: 10, 张三, 2014-07-10, 1, 北京市 <== Row: 16, 张小明, null, 1, 河南郑州 <== Row: 24, 张三丰, null, 1, 河南郑州 <== Total: 3
Choose、When、Otherwise
-
接口
List<User> selectUserList(User user);
-
sql语句
<select id="selectUserList" parameterType="user" resultType="user"> select * from user <where> <choose> <when test="username != null"> username like #{username} </when> <when test="sex != null"> sex = '男' </when> <otherwise> id = 1 </otherwise> </choose> </where> </select>
when
-when
-otherwise
的结构 相当于if
-else if
-else
-
测试
@Test public void selectUserList(){ User user = new User(); //获取SqlSession对象 SqlSession session = MybatisUtils.getSqlSession(); //获取mapper UserMapper mapper = session.getMapper(UserMapper.class); //调用方法 List<User> list = mapper.selectUserList(user); //提交事务 session.commit(); //输出结果集 System.out.println(list); //关闭连接 session.close(); }
-
结果
==> Preparing: select * from user WHERE id = 1 ==> Parameters: <== Columns: id, username, birthday, sex, address <== Row: 1, CodeYuan-Y, null, 2, null <== Total: 1
Set元素的使用
set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:
-
接口
Integer updateUserUseSet(User user);
-
sql语句
<update id="updateUserUseSet" parameterType="user"> update user <set> <if test="username != null">username = #{username},</if> <if test="sex != null">sex = #{sex},</if> address = #{address} </set> where id = #{id} </update>
-
测试
@Test public void updateUserUseSet(){ User user = new User(); user.setId(1); user.setSex("男"); user.setAddress("山东青岛"); //获取SqlSession对象 SqlSession session = MybatisUtils.getSqlSession(); //获取mapper UserMapper mapper = session.getMapper(UserMapper.class); //调用方法 mapper.updateUserUseSet(user); //提交事务 session.commit(); //关闭连接 session.close(); }
-
结果
==> Preparing: update user SET sex = ?, address = ? where id = ? ==> Parameters: 男(String), 山东青岛(String), 1(Integer) <== Updates: 1
Foreach使用
-
**item:**集合中元素迭代时的别名,该参数为必选。
-
index:在list和数组中,index是元素的序号,在map中,index是元素的key,该参数可选
-
open:foreach代码的开始符号,一般是(和close=")"合用。常用在in(),values()时。该参数可选
-
separator:元素之间的分隔符,例如在in()的时候,separator=","会自动在元素中间用“,“隔开,避免手动输入逗号导致sql错误,如in(1,2,)这样。该参数可选。
-
close: select * from user where id in #{item} foreach代码的关闭符号,一般是)和open="("合用。常用在in(),values()时。该参数可选。
-
collection: 要做foreach的对象,作为入参时,List对象默认用"list"代替作为键,数组对象有"array"代替作为键,Map对象没有默认的键。当然在作为入参时可以使用@Param(“keyName”)来设置键,设置keyName后,list,array将会失效。 除了入参这种情况外,还有一种作为参数对象的某个字段的时候。举个例子:如果User有属性List ids。入参是User对象,那么这个collection = “ids”.如果User有属性Ids ids;其中Ids是个对象,Ids有个属性List id;入参是User对象,那么collection = "ids.id"
-
接口
List<User> selectForeachById(List<Integer> list);
-
sql语句
<select id="selectForeachById" parameterType="list" resultType="user"> select * from user where id in <foreach collection="list" item="item" open=" (" separator="," close=")"> #{item} </foreach> </select>
-
测试
@Test public void selectForeachById(){ List<Integer> num = new ArrayList<Integer>(); num.add(1); num.add(10); num.add(16); //获取SqlSession对象 SqlSession session = MybatisUtils.getSqlSession(); //获取mapper UserMapper mapper = session.getMapper(UserMapper.class); //调用方法 List<User> list = mapper.selectForeachById(num); //输出结果集 for (User u : list) { System.out.println(u); } //关闭连接 session.close(); }
-
结果
==> Preparing: select * from user where id in ( ? , ? , ? ) ==> Parameters: 1(Integer), 10(Integer), 16(Integer) <== Columns: id, username, birthday, sex, address <== Row: 1, CodeYuan-Y, null, 男, 山东青岛 <== Row: 10, 张三, 2014-07-10, 1, 北京市 <== Row: 16, 张小明, null, 1, 河南郑州 <== Total: 3
缓存
本地缓存(一级缓存)
Mybatis 使用到了两种缓存:本地缓存(local cache)和二级缓存(second level cache)。
每当一个新 session 被创建,MyBatis 就会创建一个与之相关联的本地缓存。任何在 session 执行过的查询结果都会被保存在本地缓存中,所以,当再次执行参数相同的相同查询时,就不需要实际查询数据库了。本地缓存将会在做出修改、事务提交或回滚,以及关闭 session 时清空。
默认情况下,本地缓存数据的生命周期等同于整个 session 的周期。由于缓存会被用来解决循环引用问题和加快重复嵌套查询的速度,所以无法将其完全禁用。但是你可以通过设置 localCacheScope=STATEMENT 来只在语句执行时使用缓存。
你可以随时调用以下方法来清空本地缓存:
void clearCache()
确保 SqlSession 被关闭
void close()
以上面代码为例:
@Test
public void selectForeachById(){
List<Integer> num = new ArrayList<Integer>();
num.add(1);
num.add(10);
num.add(16);
//获取SqlSession对象
SqlSession session = MybatisUtils.getSqlSession();
//获取mapper
UserMapper mapper = session.getMapper(UserMapper.class);
//调用方法
List<User> list = mapper.selectForeachById(num);
System.out.println("======================================");
List<User> lis = mapper.selectForeachById(num);
//比较
System.out.println(lis.equals(list));
//关闭连接
session.close();
}
我们在调用一个方法查询一段内容后,再次调用查看日志发现,系统只进行了一次sql查询,那么另一次调用的方法干什么了呢?它是对缓存进行了查询,而且查询的内容相同。
==> Preparing: select * from user where id in ( ? , ? , ? )
==> Parameters: 1(Integer), 10(Integer), 16(Integer)
<== Columns: id, username, birthday, sex, address
<== Row: 1, CodeYuan-Y, null, 男, 山东青岛
<== Row: 10, 张三, 2014-07-10, 1, 北京市
<== Row: 16, 张小明, null, 1, 河南郑州
<== Total: 3
======================================
true
当我们在调用第一次查询方法后,清除缓存,然后再次查询会发现。
@Test
public void selectForeachById(){
List<Integer> num = new ArrayList<Integer>();
num.add(1);
num.add(10);
num.add(16);
//获取SqlSession对象
SqlSession session = MybatisUtils.getSqlSession();
//获取mapper
UserMapper mapper = session.getMapper(UserMapper.class);
//调用方法
List<User> list = mapper.selectForeachById(num);
session.clearCache();
System.out.println("===================清除缓存后===================");
List<User> lis = mapper.selectForeachById(num);
//比较
System.out.println(lis.equals(list));
//关闭连接
session.close();
}
==> Preparing: select * from user where id in ( ? , ? , ? )
==> Parameters: 1(Integer), 10(Integer), 16(Integer)
<== Columns: id, username, birthday, sex, address
<== Row: 1, CodeYuan-Y, null, 男, 山东青岛
<== Row: 10, 张三, 2014-07-10, 1, 北京市
<== Row: 16, 张小明, null, 1, 河南郑州
<== Total: 3
===================清除缓存后===================
==> Preparing: select * from user where id in ( ? , ? , ? )
==> Parameters: 1(Integer), 10(Integer), 16(Integer)
<== Columns: id, username, birthday, sex, address
<== Row: 1, CodeYuan-Y, null, 男, 山东青岛
<== Row: 10, 张三, 2014-07-10, 1, 北京市
<== Row: 16, 张小明, null, 1, 河南郑州
<== Total: 3
true
由于缓存被清除了,所有系统进行了两次查询。
-
同一个 SqlSession 中, Mybatis 会把执行的方法和参数通过算法生成缓存的键值, 将键值和结果存放在一个 Map 中, 如果后续的键值一样, 则直接从 Map 中获取数据;
-
不同的 SqlSession 之间的缓存是相互隔离的
一级缓存只在同一个SqlSession使用范围内有效
-
用一个 SqlSession, 可以通过配置使得在查询前清空缓存;
-
任何的 UPDATE, INSERT, DELETE 语句都会清空缓存。
由于内容发生了变化,缓存的内容与数据库中的不相符,所以没有存在的意义了
二级缓存
在...mapper.xml
加以下标签,需要注意的是Mybatis
使用二级缓存,实体类必须进行序列化。至于为什么要序列化?是由于缓存的key-value
的存储特性。
<cache/>
测试
@Test
public void selectForeachById(){
List<Integer> num = new ArrayList<Integer>();
num.add(1);
num.add(10);
num.add(16);
//获取SqlSession对象
SqlSession session= MybatisUtils.getSqlSession();
//获取mapper
UserMapper mapper1 = session.getMapper(UserMapper.class);
//调用方法
List<User> list = mapper1.selectForeachById(num);
//关闭连接
session.close();
System.out.println("===================重启SqlSession===================");
//获取SqlSession对象
session = MybatisUtils.getSqlSession();
//获取mapper
UserMapper mapper2 = session.getMapper(UserMapper.class);
//调用方法
List<User> lis = mapper2.selectForeachById(num);
//关闭连接
session.close();
}
结果
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@c7b4de]
==> Preparing: select * from user where id in ( ? , ? , ? )
==> Parameters: 1(Integer), 10(Integer), 16(Integer)
<== Columns: id, username, birthday, sex, address
<== Row: 1, CodeYuan-Y, null, 男, 山东青岛
<== Row: 10, 张三, 2014-07-10, 1, 北京市
<== Row: 16, 张小明, null, 1, 河南郑州
<== Total: 3
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@c7b4de]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@c7b4de]
Returned connection 13087966 to pool.
===================重启SqlSession===================
Cache Hit Ratio [com.yy.mapper.UserMapper]: 0.5
基本上就是这样。这个简单语句的效果如下:
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
提示 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。
这些属性可以通过 cache 元素的属性来修改。比如:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
可用的清除策略有:
LRU
– 最近最少使用:移除最长时间不被使用的对象。FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
默认的清除策略是 LRU。
flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
提示 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新
注解开发
-
配置映射文件
<!--注解开发--> <mappers> <mapper class="com.yy.mapper.UserMapper" /> </mappers>
-
注解
@Select("select * from user where 1 = 1") List<User> getUserList();
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
选择何种方式来配置映射,以及认为是否应该要统一映射语句定义的形式,完全取决于你和你的团队。 换句话说,永远不要拘泥于一种方式,你可以很轻松的在基于注解和 XML 的语句映射方式间自由移植和切换。
Mybatis执行的基本流程
- 第一步通过
SqlSessionFactoryBuilder
创建SqlSessionFactory
。 - 第二步通过
SqlSessionFactory
创建SqlSession
。 - 第三步通过
SqlSession
拿到Mapper
对象的代理。 - 第四步通过
MapperProxy
调用Maper
中相应的方法。
(1)读取MyBatis的配置文件。mybatis-config.xml为MyBatis的全局配置文件,用于配置数据库连接信息。
(2)加载映射文件。映射文件即SQL映射文件,该文件中配置了操作数据库的SQL语句,需要在MyBatis配置文件mybatis-config.xml中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。
(3)构造会话工厂。通过MyBatis的环境配置信息构建会话工厂SqlSessionFactory。
(4)创建会话对象。由会话工厂创建SqlSession对象,该对象中包含了执行SQL语句的所有方法。
(5)Executor执行器。MyBatis底层定义了一个Executor接口来操作数据库,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。
(6)MappedStatement对象。在Executor接口的执行方法中有一个MappedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的SQL语句的id、参数等信息。
(7)输入参数映射。输入参数类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输入参数映射过程类似于JDBC对preparedStatement对象设置参数的过程。
(8)输出结果映射。输出结果类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输出结果映射过程类似于JDBC对结果集的解析过程。
MyBatis解决了JDBC编程的问题
- 数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可解决此问题。
如何解决这个问题呢? 可以在SqlMapConfig.xml文件中配置数据库连接池,使用连接池管理数据库连接。 - SQL语句写在代码中造成代码不易维护,实际应用中SQL语句变化的可能较大,如果SQL语句一旦变动,那么就需要改变Java代码了。
如何解决这个问题呢? 可以将SQL语句配置在映射文件中与Java代码分离。 - 向SQL语句传参数麻烦,因为SQL语句的where条件不一定,可能多也可能少,修改SQL语句还要修改Java代码,导致系统不易维护。
如何解决这个问题呢? MyBatis可以自动将Java对象映射至SQL语句中,通过Statement中的parameterType定义输入参数的类型。 - 对结果集解析麻烦,SQL变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析则比较方便。
如何解决这个问题呢? MyBatis可以自动将SQL语句的执行结果映射至Java对象,通过Statement中的resultType定义输出结果的类型。