Mybatis
概述
概念
Mybatis是一个半自动化 持久化层 ORM框架
- ORM:Object Relational Mapping【对象 关系 映射】
- 将Java中的对象与数据库中表建议映射关系。

Mybatis与Hibernate对比
- Mybatis是一个半自动化【需要手写SQL】
- Hibernate是全自动化【无需手写SQL】
持久化层:dao层
Mybatis与JDBC对比
- JDBC中的SQL与Java代码耦合度高
- Mybatis将SQL与Java代码解耦,将SQL语句解耦合到配置文件之中
Java POJO(Plain Old Java Objects,普通老式 Java 对象)
- JavaBean 等同于 POJO
- Dao 等同于 mapper
官网地址
框架搭建
1、导入jar包
<!--导入MySQL的驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<!--导入MyBatis的jar包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!--导入junit的jar包-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>2、编写核心配置文件
位置:resources目标下,mybatis-config.xml
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 default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <!--mysql8版本--> <!--<property name="driver" value="com.mysql.cj.jdbc.Driver"/>--> <!--<property name="url" value="jdbc:mysql://localhost:3306/db220106?serverTimezone=UTC"/>--> <!--mysql5版本--> 下面四个配置,和德鲁伊配置文件中的一样 <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/db220106"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <!--设置映射文件路径,在resource下的路径--> <mappers> <mapper resource="mapper/EmployeeMapper.xml"/> </mappers> </configuration>
3、编写映射文件
位置:resources下mapper包,命名为:XXXMapper.xml,主要作用为Mapper接口书写Sql语句**
要求:
- 映射文件名与接口名一致
- 映射文件namespace与接口全类名一致
- 映射文件SQL的Id与接口的方法名一致:因为方法重载名一样,而映射xml文件中的id绑定的是方法名,重载的方法会绑定同一个SQL语句,所以不支持方法重载
实例代码:
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.atguigu.mybatis.mapper.EmployeeMapper"><!--接口的全类名--> <select id="selectEmpById" resultType="com.atguigu.mybatis.pojo.Employee"><!--方法和结果集全类名--> <!--SQL语句--> SELECT id, last_name, email, salary FROM tbl_employee WHERE id=#{empId} </select> </mapper>
4、使用Mybatis
先通过SqlSessionFactoryBuilder获取SqlSessionFactory对象
通过SqlSessionFactory的openSession方法再获取SqlSession对象
通过SqlSession对象获取XXXMapper代理对象
代码:
javatry { String resource = "mybatis-config.xml";//获得核心配置文件相对于resource的路径 InputStream inputStream = Resources.getResourceAsStream(resource);//得到他的输入流 //获取SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession();//获取SQLSession对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class);//获取UserMapper的代理对象 System.out.println(mapper.getUserById(7));//通过代理对象执行方法 int count = sqlSession.insert("insertCar"); // 这个"insertCar"必须是sql的id System.out.println("插入几条数据:" + count); //提交(mybatis默认采用的事务管理器是JDBC,默认是不提交的,需要手动提交。) sqlSession.commit(); //关闭资源(只关闭是不会提交的) sqlSession.close(); } catch (IOException e) { e.printStackTrace(); }
Mybatis对象作用域
SqlSessionFactoryBuilder
一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:
Mybatis通过XMl配置
Mybatis核心配置文件
根标签:
- configuration(配置),没有实际语义,主要作用:所有子标签均需要设置在跟标签内部
常用子标签:
properties(属性):定义或引入外部属性文件
示例代码
properties新建的properties文件,并按照键值对形式写上值: #key=value driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/javaweb?serverTimezone=UTC name=root //username是系统变量名,不能使用 password=Fujianz123xml<!--引入properties配置文件,resource表示从resource路径下开始,使用${}:--> <properties resource="db.properties"></properties> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${db.driver}"/> <property name="url" value="${db.url}"/> <property name="username" value="${db.name}"/> <property name="password" value="${db.password}"/> </dataSource> </environment> </environments>
settings子标签:调整设置MyBatis
字段名映射(mapUnderscoreToCamelCase):是否开启驼峰命名自动映射,默认值false,如设置true会自动将字段a_col与aCol属性自动映射【只能将字母相同的字段与属性自动映射】
类型别名(typeAliases):类型别名可为 Java 类型设置一个缩写名字,目的是使用时无需写全类名
xml<typeAliases> <!--指定类型定义别名--> <typeAlias type="com.atguigu.mybatis.pojo.Employee" alias="employee"></typeAlias>--> <!--为指定包下所有的类定义别名,默认将类名作为别名,不区分大小写【推荐使用小写字母】--> <package name="com.atguigu.mybatis.pojo"/> </typeAliases>Mybatis自定义别名:
别名 类型 _int int【基本数据类型为前面加下划线】 integer或int Integer【包装数据类型的别名为基本数据类型】 string String list或arraylist ArrayList map或hashmap HashMap
环境配置(environments):设置数据库连接环境
示例代码
xml<!--设置数据库连接环境--> <!--可以配置多个连接环境,默认环境为production--> <environments default="production"> <!--开发环境--> <environment id="dev"> <!--配置事务管理器为JDBC--> <transactionManager type="JDBC"/> <!--设置连接池,连接池,每一次都会新建JDBC连接对象。POOLED会使用数据库连接池(mybatis自己实现的)。--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/powernode"/> <property name="username" value="root"/> <property name="password" value="root"/> <!--最大连接数--> <property name="poolMaximumActiveConnections" value="3"/> <!--这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。--> <property name="poolTimeToWait" value="20000"/> <!--强行回归池的时间--> <property name="poolMaximumCheckoutTime" value="20000"/> <!--最多空闲数量--> <property name="poolMaximumIdleConnections" value="1"/> </dataSource> </environment> <!--生产环境--> <environment id="production"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments>- java
@Test public void testEnvironment() throws Exception{ // 准备数据 Car car = new Car(); car.setCarNum("133"); car.setBrand("丰田霸道"); car.setGuidePrice(50.3); car.setProduceTime("2020-01-10"); car.setCarType("燃油车"); // 一个数据库对应一个SqlSessionFactory对象 // 两个数据库对应两个SqlSessionFactory对象,以此类推 SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); // 使用默认数据库 SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml")); SqlSession sqlSession = sqlSessionFactory.openSession(true); int count = sqlSession.insert("insertCar", car); System.out.println("插入了几条记录:" + count); // 使用指定数据库 SqlSessionFactory sqlSessionFactory1 = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"), "dev"); SqlSession sqlSession1 = sqlSessionFactory1.openSession(true); int count1 = sqlSession1.insert("insertCar", car); System.out.println("插入了几条记录:" + count1); }
mappers子标签:设置映射文件路径,resource表示从资源文件下开始加载的数据
示例代码
xml<!--设置映射文件路径--> <mappers> <!--使用绝对路径,从指定目录下开始查找--> <mapper url="file:///var/mappers/AuthorMapper.xml"/> <!--从资源目录下开始查找--> <mapper resource="mapper/EmployeeMapper.xml"/> <!-- SQL映射文件和mapper接口放在同一个目录下。 SQL映射文件的名字也必须和mapper接口名一致。 --> <!--告诉MyBatis去加载com.powernode.mybatis.mapper.CarMapper类。MyBatis将会查找这个类中定义的SQL映射。--> <mapper class="com.powernode.mybatis.mapper.CarMapper"/> <!-- 通过package设置,要求接口的包名与映射文件的包名需要一致,查找这个包及其子包中所有的类,并加载其中定义的SQL映射。--> <package name="com.atguigu.mybatis.mapper"/> </mappers>
子标签配置顺序:
configuration:根标签,表示配置信息。
environments:环境(多个),以“s”结尾表示复数,也就是说mybatis的环境可以配置多个数据源。
- default属性:表示默认使用的是哪个环境,default后面填写的是environment的id。default的值只需要和environment的id值一致即可。
environment:具体的环境配置(主要包括:事务管理器的配置 + 数据源的配置)
- id:给当前环境一个唯一标识,该标识用在environments的default后面,用来指定默认环境的选择。
transactionManager:配置事务管理器
- type属性:指定事务管理器具体使用什么方式,可选值包括两个
- JDBC:使用JDBC原生的事务管理机制。底层原理:事务开启conn.setAutoCommit(false); ...处理业务...事务提交conn.commit();
- MANAGED:交给其它容器来管理事务,比如WebLogic、JBOSS等。如果没有管理事务的容器,则没有事务。没有事务的含义:只要执行一条DML语句,则提交一次。
dataSource:指定数据源
- type属性:用来指定具体使用的数据库连接池的策略,可选值包括三个
- UNPOOLED:采用传统的获取连接的方式,虽然也实现Javax.sql.DataSource接口,但是并没有使用池的思想。
- property可以是:
- driver 这是 JDBC 驱动的 Java 类全限定名。
- url 这是数据库的 JDBC URL 地址。
- username 登录数据库的用户名。
- password 登录数据库的密码。
- defaultTransactionIsolationLevel 默认的连接事务隔离级别。
- defaultNetworkTimeout 等待数据库操作完成的默认网络超时时间(单位:毫秒)
- POOLED:采用传统的javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实现。
- property可以是(除了包含UNPOOLED中之外):
- poolMaximumActiveConnections 在任意时间可存在的活动(正在使用)连接数量,默认值:10
- poolMaximumIdleConnections 任意时间可能存在的空闲连接数。
- 其它....
- JNDI:采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到DataSource是不一样。如果不是web或者maven的war工程,JNDI是不能使用的。
- property可以是(最多只包含以下两个属性):
- initial_context 这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))这是个可选属性,如果忽略,那么将会直接从 InitialContext 中寻找 data_source 属性。
- data_source 这是引用数据源实例位置的上下文路径。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。
mappers:在mappers标签中可以配置多个sql映射文件的路径。
mapper:配置某个sql映射文件的路径
- resource属性:使用相对于类路径的资源引用方式
- url属性:使用完全限定资源定位符(URL)方式

Mybatis映射文件
根标签:mapper标签:
- mapper中的namespace要求与接口的全类名一致,mybatis在初始化时,会把sql的id作为key、sql语句作为值放到一个Map集合里,待到执行的时候,会根据方法名去Map集合里获取对应的sql语句,如果此时方法名和sql的id不一致,就获取不到对应的sql语句。
curd子标签:
insert标签:定义添加SQL
delete标签:定义删除SQL
update标签:定义修改SQL
select标签:定义查询SQL
sql标签
sql标签:定义可重用的SQL语句块
include标签:导入SQL语句块
xml<sql id="hah"> id,username,password </sql> <select id="query" resultType="User"> select <include refid="hah"/> <!--将id为hah的SQL语句块导入--> from t_user </select>
Cache标签
cache标签:设置当前命名空间的缓存配置
cache-ref标签:设置其他命名空间的缓存配置
resultType和resultMap
描述如何从数据库结果集中加载对象。resultType用于基本类型或简单对象类型,resultMap用于自定义映射关系
- 设置期望结果集返回类型:resultType:【全类名或别名】
- 注意:如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。
- resultType 和 resultMap 之间只能同时使用一个。
获取主键自增数据
useGeneratedKeys:启用主键生成策略
keyProperty:设置数据主键值储存于对象的哪一个属性。
xml<!--插入后,主键id会自动插入到对象的id属性--> <insert id="add" useGeneratedKeys="true" keyProperty="id"> INSERT INTO `user`(username,`password`,email) VALUES(#{username},#{password},#{email}) </insert>
获取数据库受影响行数
- 直接将接口中方法的返回值设置为int或boolean即可
- int:代表受影响行数
- boolean:代表有无影响数据库
延迟加载【懒加载】
需要时加载,不需要暂时不加载,提升程序运行效率
全局设置:开启懒加载并且关闭aggressiveLazyLoading
xml<!-- 开启延迟加载 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 设置加载的数据是按需加载3.4.2及以后的版本该步骤可省略--> <setting name="aggressiveLazyLoading" value="false"/>局部设置
与全局设置不冲突,有则优先
fetchType
- eager:关闭局部延迟加载
- lazy:开启局部延迟加载
示例代码
mysql<association property="dept" select="com.atguigu.mybatis.mapper.DeptMapper.selectDeptByDeptId" column="dept_id" fetchType="eager"> </association>
查询参数传递
SQL语句需要的参数由调用接口方法者传进。
单个普通参数
任意使用,参数数据类型、参数名称不用考虑。
多个普通参数
使用pram或者arg
Mybatis底层封装为Map结构,封装key为param1、param2....【支持:arg0、arg1、...】
<select id="getUserById" resultType="User">
SELECT * FROM
`user`
WHERE id = #{param1} AND email=#{param2}
</select>使用命名参数
@Param(value="参数名")或者@Param("参数名"),底层依旧封装为Map结构。依然支持参数【param1,param2,...】,但是不支持【arg0,arg1】。
public List<Employee> selectEmpByNamed(@Param("lName")String lastName, @Param("salary") double salary);
<select id="selectEmpByNamed" resultType="employee">
SELECT
id,
last_name,
email,
salary
FROM
tbl_employee
WHERE
last_name=#{lName}
AND
salary=#{salary}
</select>使用POJO参数
Mybatis支持POJO【JavaBean】入参,参数key是POJO中属性。
#注意,此处#{username}是通过get方法获得的,即将username首字母大写,拼接上get===>getUsername
<select id="selectEmpByNamed" resultType="employee">
SELECT
id,
last_name,
email,
salary
FROM
tbl_employee
WHERE
last_name=#{username}//直接使用对象中的username属性
AND
salary=#{salary}//直接使用对象中的salary属性
</select>使用Map参数
Mybatis支持直接Map入参,map的key=参数key,不支持param1、param2
//传一个Map
int count = sqlSession.insert("insertCar", map);
//插入语句直接使用插入map中的key值
<insert id="insertCar">
insert into t_car(car_num,brand,guide_price) values(#{carNum},#{brand},#{guidePrice})
</insert>#与$:
Statement:先进行sql语句拼接,然后再编译sql语句,底层是Statement实现。存在sql注入现象。只有在需要进行sql语句关键字拼接的情况下才会用到。
PreparedStatement:先编译sql语句,再给占位符传值,底层是PreparedStatement实现。可以防止sql注入。入参使用 ?作为占位符方式
二者使用场景:
#使用场景,sql占位符位置均可以使用#,能用 #{} 就不用 ${}
$使用场景,动态化表名、sql拼接等,占位符不支持动态化表名
xml<!-- 使用#{}会是这样:select * from 't_car' 使用${}会是这样:select * from t_car --> <select id="selectEmpByDynamitTable" resultType="employee"> SELECT * FROM ${tblName} </select> <!--如果使用#进行拼接,会在后面添加上单引号:select * from t_car order by carNum 'desc'--> <select id="selectAll" resultType="com.powernode.mybatis.pojo.Car"> select * from t_car order by carNum ${key} </select> <!-- 使用#{} :delete from t_user where id in('1,2,3') 使用${} :delete from t_user where id in(1, 2, 3) --> <!--模糊查询拼接参数--> <!--使用$--> <select id="selectLikeByBrand" resultType="Car"> select * from t_car where brand like '%${brand}%' </select> <select id="selectLikeByBrand" resultType="Car"> select * from t_car where brand like concat('%',#{brand},'%') </select> <!--使用#--> <select id="selectLikeByBrand" resultType="Car"> select * from t_car where brand like "%"#{brand}"%" </select>
查询参数返回
单行数据返回单个对象
//可以使用对象接收,也可以使用集合列表接收
public Employee selectEmpById(int empId);<select id="selectEmpById" resultType="employee">
SELECT
id,
last_name,
email,
salary
FROM
tbl_employee
WHERE
id=#{empId}
</select>多行数据返回对象的集合
public List<Employee> selectAllEmps();#resultType设置为集合的元素类型
<select id="selectAllEmps" resultType="employee">
SELECT
id,
last_name,
email,
salary
FROM
tbl_employee
</select>单行数据返回Map集合
返回值类型Map<String key,Object value>
字段作为Map的key,字段的结果作为Map的Value
{password=123456, sex=男 , id=1, age=23, username=admin}示例代码
javapublic Map<String,Object> selectEmpReturnMap(int empId);xml<!--查询单行数据返回Map集合--> <select id="selectEmpReturnMap" resultType="map"> SELECT id, last_name, email, salary FROM tbl_employee WHERE id=#{empId} </select>
多行数据返回Map集合
- 使用List<Map>作为返回值
List<Map<String,Object>> selectAllRetListMap();- 使用Map<Integer key,Employee value>返回值类型
//指定一个字段作为返回Map中的key,一般使用唯一键来做key
@MapKey("id")
public Map<Integer,Employee> selectEmpsReturnMap();<select id="selectEmpsReturnMap" resultType="map">
SELECT
id,
last_name,
email,
salary
FROM
tbl_employee
</select>resultMap自定义映射
自动映射与自定义映射:
自动映射【resultType】:指的是自动将表中的字段与类中的属性进行关联映射
- 自动映射解决不了两类问题
- 多表连接查询时,需要返回多张表的结果集【即对象内有对象】
- 单表查询时,没有开启支持驼峰式自动映射,并且不想为字段定义别名
自定义映射【resultMap】:自动映射解决不了问题,交给自定义映射
自定义映射类型
级联映射
#例:employee对象中存在dept对象
#自定义映射:id为该映射的标识,employee标识该映射的类类型
<resultMap id="empAndDeptResultMap" type="employee">
#定义主键字段与属性关联关系
<id column="id" property="id"></id>
#定义非主键字段与属性关联关系
<result column="last_name" property="lastName"></result>
<result column="email" property="email"></result>
<result column="salary" property="salary"></result>
#为员工中所属部门,自定义关联关系,通过表名.的方式给column赋值
<result column="dept_id" property="dept.deptId"></result>
<result column="dept_name" property="dept.deptName"></result>
</resultMap>
<select id="selectEmpAndDeptByEmpId" resultMap="empAndDeptResultMap">
SELECT
e.`id`,
e.`email`,
e.`last_name`,
e.`salary`,
d.`dept_id`,
d.`dept_name`
FROM
tbl_employee e,
tbl_dept d
WHERE
e.`dept_id` = d.`dept_id`
AND
e.`id` = #{empId}
</select>association映射
解决一对一映射关系,即对象内还存在对象
<!-- 自定义映射 【员工与部门关系】-->
<resultMap id="empAndDeptResultMapAssociation" type="employee">
<!-- 定义主键字段与属性关联关系 -->
<id column="id" property="id"></id>
<!-- 定义非主键字段与属性关联关系-->
<result column="last_name" property="lastName"></result>
<result column="email" property="email"></result>
<result column="salary" property="salary"></result>
<!--为员工中所属部门,自定义关联关系,dept为员工对象中的部门对象属性-->
<association property="dept" javaType="com.atguigu.mybatis.pojo.Dept"> <!--此处对象也可以使用别名-->
<id column="dept_id" property="deptId"></id>
<result column="dept_name" property="deptName"></result>
</association>
</resultMap>association分布查询
将多表连接查询,改为【分步单表查询】,从而提高程序运行效率
<resultMap id="getUserAndOrder" type="Order">
<id column="order_id" property="orderId"></id>
<result column="order_sequence" property="orderSequence"></result>
<result column="create_time" property="createTime"></result>
<!--select:设置分布查询SQL的路径
column:将sql查询结果中的某个字段设置为分步查询下一步的参数-->
<association property="user" select="getUserById" column="user_id"></association>
</resultMap>
<select id="getOrderAndUser" resultMap="getUserAndOrder">
SELECT * FROM t_order WHERE order_id=#{id}
</select>
<select id="getUserById" resultType="User">
SELECT * FROM
`user`
WHERE id = #{id}
</select>collection映射
一对多,即对象内存在一个对象合集
示例代码
javapublic Dept selectDeptAndEmpByDeptId(int deptId);xml<resultMap id="deptAndempResultMap" type="dept"> <id property="deptId" column="dept_id"></id> <result property="deptName" column="dept_name"></result> <!--property:设置代表对象数组的属性值 ofType:设置collection标签所处理的集合属性中存储数据的类型 --> <collection property="empList" ofType="com.atguigu.mybatis.pojo.Employee"> <id column="id" property="id"></id> <result column="last_name" property="lastName"></result> <result column="email" property="email"></result> <result column="salary" property="salary"></result> </collection> </resultMap> <select id="selectDeptAndEmpByDeptId" resultMap="deptAndempResultMap"> SELECT e.`id`, e.`email`, e.`last_name`, e.`salary`, d.`dept_id`, d.`dept_name` FROM tbl_employee e, tbl_dept d WHERE e.`dept_id` = d.`dept_id` AND d.dept_id = #{deptId} </select>
collection分布查询
注意:如果分步查询时,需要传递给调用的查询中多个参数,则需要将多个参数封装成Map来进行传递,语法如下column**: {k1=v1, k2=v2....}使用该种方式,则对应对应SQL语句中必须使用对应key值**。
xml<resultMap id="get" type="User"> <!--第一张表中查询的数据--> <id column="id" property="id"></id> <result column="username" property="username"></result> <result column="email" property="email"></result> <result column="password" property="password"></result> <!--第二张表中的数据--> <collection property="order" select="com.fjut.mapper.UserMapper.getAllOrder" column="id"> </collection> </resultMap> <select id="getUserById" resultMap="get"> SELECT * FROM `user` WHERE id = #{id} </select> <select id="getAllOrder" resultType="Order"> select * from t_order where user_id=#{id} </select>
resultMap子标签
- id标签:定义主键字段与属性关联关系
- result标签:定义非主键字段与属性关联关系
- column属性:定义表中字段名称
- property属性:定义类中属性名称
- association标签:定义一对一的关联关系
- property:定义关联关系属性,即对象中的对象属性,使用属性名字,而不是类型
- javaType:定义关联关系属性的类型
- select:设置分步查询SQL全路径,即对应配置文件的路径加.SQL语句
- colunm:设置分步查询SQL中需要参数
- fetchType:设置局部延迟加载【懒加载】是否开启
- collection标签:定义一对多的关联关系
- property:定义一对一关联关系属性
- ofType:定义一对一关联关系属性类型
- fetchType:设置局部延迟加载【懒加载】是否开启
动态SQL
xml中SQL中注释:
- 方式一:-- 1=1 注释结果显示在SQL语句中
- 方式二:<!-- 1=1 --> 注释结果不显示在SQL语句中
if标签
用于完成简单的判断,test标签用于获得判断结果的真假
<select id="selectByMultiCondition" resultType="car">
select * from t_car
<!--在where后面添加上一个恒为true的表达式,确保sql一定是正确的-->
where and 0=0
<if test="guidePrice != null and guidePrice != ''">
and guide_price >= #{guidePrice}
</if>
<if test="carType != null and carType != ''">
and car_type = #{carType}
</if>
</select>where标签
用于解决where关键字及where后第一个and或or的问题,
- 所有条件都为空时,where标签保证不会生成where子句。
- 自动去除某些条件前面多余的and或or。
<select id="selectByMultiConditionWithWhere" resultType="car">
select * from t_car
<where>
<if test="brand != null and brand != ''">
and brand like #{brand}"%"
</if>
<if test="guidePrice != null and guidePrice != ''">
and guide_price >= #{guidePrice}
</if>
<if test="carType != null and carType != ''">
and car_type = #{carType}
</if>
</where>
</select>trim标签
可以在条件判断完的SQL语句前后添加或者去掉指定的字符,只有内部的if有进入时,才会执行
- prefix: 添加前缀
- prefixOverrides: 去掉前缀
- suffix: 添加后缀
- suffixOverrides: 去掉后缀
<select id="selectByMultiConditionWithTrim" resultType="car">
select * from t_car
<trim prefix="where" suffixOverrides="and|or">
<if test="brand != null and brand != ''">
brand like #{brand}"%" and
</if>
<if test="guidePrice != null and guidePrice != ''">
guide_price >= #{guidePrice} and
</if>
<if test="carType != null and carType != ''">
car_type = #{carType}
</if>
</trim>
</select>set标签
主要用于解决set关键字及多出一个【,】问题
<update id="updateEmpByOpr">
update
tbl_employee
<set>
<if test="lastName != null">
last_name=#{lastName},
</if>
<if test="email != null">
email=#{email},
</if>
<if test="salary != null">
salary=#{salary}
</if>
</set>
where
id = #{id}
</update>choose、When标签
类似java中switch-case结构,按顺序判断其内部when标签中的test条件出否成立,如果有一个成立,则 choose 结束,当 choose中所有 when 的条件都不满足时,则执行 otherwise 中的sql。
<select id="selectEmpByOneOpr" resultType="employee">
select
<include refid="emp_col"></include>
from
tbl_employee
<where>
<choose>
<when test="id != null">
id = #{id}
</when>
<when test="lastName != null">
last_name = #{lastName}
</when>
<when test="email != null">
email = #{email}
</when>
<when test="salary != null">
salary = #{salary}
</when>
<otherwise>
1=1
</otherwise>
</choose>
</where>
</select>
<!--等同于-->
if(){
}else if(){
}
……………
else{
}foreach标签
类似java中for循环
- collection: 要迭代的集合或数组
- item: 当前从集合或数组中迭代出的元素
- separator: 元素与元素之间的分隔符
- open:foreach标签中所有内容的开始
- close:foreach标签中所有内容的结束
<!--通过in批量删除-->
<!--拼接后:delete from t_car where id in(1,2,3);-->
int deleteBatchByForeach(@Param("ids") Long[] ids);
<delete id="deleteBatchByForeach">
delete from t_car where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
<!--通过or批量删除-->
<!--拼接后:delete from t_car where id = 1 or id = 2 or id = 3;-->
int deleteBatchByForeach2(@Param("ids") Long[] ids);
<delete id="deleteBatchByForeach2">
delete from t_car where
<foreach collection="ids" item="id" separator="or">
id = #{id}
</foreach>
</delete>
<!--批量添加-->
<!--
拼接后:insert into t_car values
(null,'1001','凯美瑞',35.0,'2010-10-11','燃油车'),
(null,'1002','比亚迪唐',31.0,'2020-11-11','新能源'),
(null,'1003','比亚迪宋',32.0,'2020-10-11','新能源')
-->
int insertBatchByForeach(@Param("cars") List<Car> cars);
<insert id="insertBatchByForeach">
insert into t_car values
<foreach collection="cars" item="car" separator=",">
(null,#{car.carNum},#{car.brand},#{car.guidePrice},#{car.produceTime},#{car.carType})
</foreach>
</insert>sql标签
提取可重用SQL片段
include标签
引入SQL片段
<sql id="emp_col">
id,
last_name,
email,
salary
</sql>
<!--在需要使用的地方引入即可-->
select
<include refid="emp_col"></include>
from
tbl_employee缓存机制
一级缓存:
本地缓存(Local Cache)或SqlSession级别缓存,其默认开启,不能关闭,可以清空。
缓存原理:
- 第一次获取数据时,先从数据库中加载数据,将数据缓存至Mybatis一级缓存中【缓存底层实现原理Map,key:hashCode+查询的SqlId+编写的sql查询语句+参数】
- 以后再次获取数据时,先从一级缓存中获取,如未获取到数据,再从数据库中获取数据。
在Spring中,如果没有启用事务管理,那么每次对数据库的操作(如查询、更新等)都会在一个新的SqlSession中进行,MyBatis的一级缓存的作用会大大减弱
一级缓存五种失效情况
不同的SqlSession对应不同的一级缓存,即更换了SQLSession
同一个SqlSession但是查询条件不同,即key不同
同一个SqlSession两次查询期间执行了任何一次增删改操作
增删改操作会清空一级缓存
同一个SqlSession两次查询期间手动清空了缓存
sqlSession.clearCache()
同一个SqlSession两次查询期间提交了事务
sqlSession.commit()
二级缓存
即全局作用域缓存,SqlSessionFactory级别缓存,二级缓存的作用范围是同一命名空间下的多个会话共享,这里的命名空间就是映射文件的namespace(同一个SQLSessionFactory创造出的共用同一个二级缓存)。二级缓存默认关闭,需要开启才能使用,提交sqlSession或关闭sqlSession时,才会缓存。
二级缓存使用的步骤:
① 全局配置文件中开启二级缓存<setting name="cacheEnabled" value="true"/>
② 需要使用二级缓存的映射文件处使用cache配置缓存<cache />
③ 注意:POJO需要实现Serializable接口
④ 关闭sqlSession或提交sqlSession时,将数据缓存到二级缓存
二级缓存底层原理
- 第一次获取数据时,先从数据库中获取数据,将数据缓存至一级缓存;当提交或关闭SqlSession时,将数据缓存至二级缓存
- 以后再次获取数据时,MyBatis中的二级缓存开启时,每次查询会先去二级缓存中命中查询结果,未命中时才会使用一级缓存以及直接去查询数据库。
二级缓存相关属性
- eviction=“FIFO”:缓存清除【回收】策略。
- LRU:Least Recently Used。最近最少使用。优先淘汰在间隔时间内使用频率最低的对象。(其实还有一种淘汰算法LFU,最不常用。)
- FIFO:First In First Out。一种先进先出的数据缓存器。先进入二级缓存的对象最先被淘汰。
- SOFT:软引用。淘汰软引用指向的对象。具体算法和JVM的垃圾回收算法有关。
- WEAK:弱引用。淘汰弱引用指向的对象。具体算法和JVM的垃圾回收算法有关。
- flushInterval:刷新间隔,单位毫秒。
- 默认不设置,代表不刷新缓存,只要内存足够大,一直会向二级缓存中缓存数据。除非执行了增删改。
- size:引用数目,正整数
- 代表缓存最多可以储存多少个对象,避免内存溢出
- readOnly:只读,true/false
- true:多条相同的sql语句执行之后返回的对象是共享的同一个。性能好,但是多线程并发可能会存在安全问题。
- false:多条相同的sql语句执行之后返回的对象是副本,调用了clone方法。性能一般。但安全。
- eviction=“FIFO”:缓存清除【回收】策略。
二级缓存的失效情况
- 当SqlSession执行了任何一种更新操作(包括插入、更新、删除)时,这个SqlSession对应的Mapper下的二级缓存都会被清空
- sqlSession.clearCache():只是用来清除一级缓存。
第三方缓存
mybatis对外提供了接口,也可以集成第三方的缓存组件。比如EhCache、Memcache、redis等
注意:第三方缓存,需要建立在二级缓存基础上【需要开启二级缓存,第三方缓存才能生效】,二级缓存设置失效,则第三方缓存也将失效。
使用步骤
导入jar包
xml<!-- mybatis-ehcache --> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.0.3</version> </dependency> <!-- slf4j-log4j12 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.2</version> <scope>test</scope> </dependency>编写配置文件【ehcache.xml】
xml<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false"> <!--磁盘存储:将缓存中暂时不使用的对象,转移到硬盘,类似于Windows系统的虚拟内存--> <diskStore path="e:/ehcache"/> <!--defaultCache:默认的管理策略--> <!--eternal:设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断--> <!--maxElementsInMemory:在内存中缓存的element的最大数目--> <!--overflowToDisk:如果内存中数据超过内存限制,是否要缓存到磁盘上--> <!--diskPersistent:是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false--> <!--timeToIdleSeconds:对象空闲时间(单位:秒),指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0,表示一直可以访问--> <!--timeToLiveSeconds:对象存活时间(单位:秒),指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0,表示一直可以访问--> <!--memoryStoreEvictionPolicy:缓存的3 种清空策略--> <!--FIFO:first in first out (先进先出)--> <!--LFU:Less Frequently Used (最少使用).意思是一直以来最少被使用的。缓存的元素有一个hit 属性,hit 值最小的将会被清出缓存--> <!--LRU:Least Recently Used(最近最少使用). (ehcache 默认值).缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存--> <defaultCache eternal="false" maxElementsInMemory="1000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU"/> </ehcache>加载第三方缓存【映射文件】
xml<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>EHCache配置文件说明
属性名 是否必须 作用 maxElementsInMemory 是 在内存中缓存的element的最大数目 maxElementsOnDisk 是 在磁盘上缓存的element的最大数目,若是0表示无穷大 eternal 是 设定缓存的elements是否永远不过期。 如果为true,则缓存的数据始终有效, 如果为false那么还要根据timeToIdleSeconds、timeToLiveSeconds判断 overflowToDisk 是 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上 timeToIdleSeconds 否 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时, 这些数据便会删除,默认值是0,也就是可闲置时间无穷大 timeToLiveSeconds 否 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大 diskSpoolBufferSizeMB 否 DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区 diskPersistent 否 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false diskExpiryThreadIntervalSeconds 否 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s, 相应的线程会进行一次EhCache中数据的清理工作 memoryStoreEvictionPolicy 否 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。 默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出
mybatis逆向工程
概念:
正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate是支持正向工程的
逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:
- Java实体类
- Mapper接口
- Mapper映射文件
步骤: mybatis-generator仅限单表查询!!!
添加依赖和插件
xml<dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.3.2</version> </dependency>创建逆向工程的配置文件:位置:resource,命名mbg.xml
xml<!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <context id="simple" targetRuntime="MyBatis3"> <!--jdbc的驱动,路径,和数据库账号,密码--> <jdbcConnection driverClass="org.hsqldb.jdbcDriver" connectionURL="jdbc:hsqldb:mem:aname"userId="root" password="123456"> </jdbcConnection> <!-- javaBean的生成策略,targetPackage:生成的bean放在哪个包下,targetProject:存放bean所在包的包 只需要写到包为止,名字可以自己生成--> <javaModelGenerator targetPackage="example.model" targetProject="src/main/java"/> <!--SQL映射文件的生成策略--> <sqlMapGenerator targetPackage="example.mapper" targetProject="src/main/resources"/> <!--Mapper接口的生成策略--> <javaClientGenerator type="XMLMAPPER" targetPackage="example.mapper" targetProject="src/main/java"/> <!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName --> <!-- domainObjectName属性指定生成出来的实体类的类名 --> <table tableName="FooTable" domainObjectName="Emp"/> </context> </generatorConfiguration>xml<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <!-- targetRuntime: 执行生成的逆向工程的版本 MyBatis3Simple: 生成基本的CRUD(清新简洁版) MyBatis3: 生成带条件的CRUD(奢华尊享版) --> <context id="DB2Tables" targetRuntime="MyBatis3Simple"> <!-- 数据库的连接信息 --> <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/mybatis" userId="root" password="123456"> </jdbcConnection> <!-- javaBean的生成策略--> <javaModelGenerator targetPackage="com.atguigu.mybatis.pojo" targetProject=".\src\main\java"> <property name="enableSubPackages" value="true" /> <property name="trimStrings" value="true" /> </javaModelGenerator> <!-- SQL映射文件的生成策略 --> <sqlMapGenerator targetPackage="com.atguigu.mybatis.mapper" targetProject=".\src\main\resources"> <property name="enableSubPackages" value="true" /> </sqlMapGenerator> <!-- Mapper接口的生成策略 --> <javaClientGenerator type="XMLMAPPER" targetPackage="com.atguigu.mybatis.mapper" targetProject=".\src\main\java"> <property name="enableSubPackages" value="true" /> </javaClientGenerator> <!-- 逆向分析的表 --> <!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName --> <!-- domainObjectName属性指定生成出来的实体类的类名 --> <table tableName="t_emp" domainObjectName="Emp"/> <table tableName="t_dept" domainObjectName="Dept"/> </context> </generatorConfiguration>运行:注意,生成完之后,需要在核心配置文件中添加映射配置文件的路径
javaList<String> warnings = new ArrayList<String>(); boolean overwrite = true; File configFile = new File("generatorConfig.xml");//使用的不是source文件下的路径,是从src下开始 ConfigurationParser cp = new ConfigurationParser(warnings); Configuration config = cp.parseConfiguration(configFile); DefaultShellCallback callback = new DefaultShellCallback(overwrite); MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings); myBatisGenerator.generate(null);
生成类型:
- MyBatis3Simple
- 生成的只有简单的增删改查,凭借主键。在pojo下只生成一个类,类中包含get,set方法,但是没有构造器和toString
- MyBatis3
- 又称为QBC,即按条件的增删改查。在pojo下只生成两个个类。普通类中包含get,set方法,但是没有构造器和toString。Example类中包含的是需要添加的条件。调用接口中的selectByExample方法,传入example对象,即可按条件查询。
java条件查询: 1、new一个Example类 2、Example获取内部类Criteria,通过Criteria添加条件 String resource ="mybatis-config.xml"; InputStream resourceAsStream = Resources.getResourceAsStream(resource); SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = build.openSession(); //mapper可以直接通过主键等简单条件进行查询 orderItemMapper mapper = sqlSession.getMapper(orderItemMapper.class); //创建Example条件 orderItemExample orderItem=new orderItemExample(); orderItemExample.Criteria criteria=orderItem.createCriteria(); criteria.andBookNameEqualTo("苏东坡传"); //将Example条件加入,按条件查询 List<orderItem> orderItems = mapper.selectByExample(orderItem); for (orderItem item : orderItems) { System.out.println(item); 条件为空:查询所有内容- MyBatis3Simple
分页插件
引入jar包:
xml<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.2.0</version> </dependency>核心配置文件中配置插件:
xml<plugins> <!--设置分页插件--> <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin> </plugins>使用:
a>在查询功能之前使用PageHelper.startPage(int pageNum, int pageSize)开启分页功能
- pageNum:开始分页的页码
- pageSize:每页显示的条数
b>在查询获取list集合之后,将其返回值封装到PageInfo中
java//开启分页 Page<Object> objects = PageHelper.startPage(1, 3); //进行查询 List<orderItem> orderItems = mapper.selectByExample(orderItem); //PageInfo进行封装 PageInfo pageInfo=new PageInfo(objects);c>分页数据
javaPageInfo{ pageNum=8, pageSize=4, size=2, startRow=29, endRow=30, total=30, pages=8, list=Page{count=true, pageNum=8, pageSize=4, startRow=28, endRow=32, total=30, pages=8, reasonable=false, pageSizeZero=false}, prePage=7, nextPage=0, isFirstPage=false, isLastPage=true, hasPreviousPage=true, hasNextPage=false, navigatePages=5, navigateFirstPage4, navigateLastPage8, navigatepageNums=[4, 5, 6, 7, 8] } pageNum:当前页的页码 pageSize:每页显示的条数 size:当前页显示的真实条数 total:总记录数 pages:总页数 prePage:上一页的页码 nextPage:下一页的页码 isFirstPage/isLastPage:是否为第一页/最后一页 hasPreviousPage/hasNextPage:是否存在上一页/下一页 navigatePages:导航分页的页码数,即显示几页 navigatepageNums:导航分页的页码,[1,2,3,4,5] //分页需要数据如下 pageInfo.getPageNum():当前页 pageInfo.getPages():总页数 pageInfo.getTotal():总数据数量 pageInfo.getPagesize():每页显示数据数量 pageInfo.getList():当前页显示数据集合 pageInfo.isHasPreviousPage():是否有上一页 pageInfo.getPrePage():上一页是 pageInfo.isHasNextPage():是否有下一页 pageInfo.getNextPage():下一页是 pageInfo.isIsFirstPage():是否是第一页 pageInfo.isIsLastPage():是否是最后一页 pageInfo.getNavigateFirstPage():导航页的第一个页码是 pageInfo.getNavigateLastPage()):导航页的最后一个页码是 pageInfo.getNavigatepages()):导航页设置显示的总页码 pageInfo.getNavigatepageNums():导航页的当前显示的页码
Mybatis注解式开发
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
@Insert
@Insert(value="insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})")
int insert(Car car);@Delete
@Delete("delete from t_car where id = #{id}")
int deleteById(Long id);@Update
@Update("update t_car set car_num=#{carNum},brand=#{brand},guide_price=#{guidePrice},produce_time=#{produceTime},car_type=#{carType} where id=#{id}")
int update(Car car);@Select
@Select("select * from t_car where id = #{id}")
//如果数据库列的名称和Java对象的属性名称完全相同,那么你不需要使用@Results注解
@Results({
@Result(column = "id", property = "id", id = true),
@Result(column = "car_num", property = "carNum"),
@Result(column = "brand", property = "brand"),
@Result(column = "guide_price", property = "guidePrice"),
@Result(column = "produce_time", property = "produceTime"),
@Result(column = "car_type", property = "carType")
})
Car selectById(Long id);