MyBatis官网
MyBatis 是支持普通 SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis 消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。MyBatis 使用简单的 XML或注解用于配置和原始映射,将接口和 Java 的POJOs(Plain Ordinary Java Objects,普通的 Java对象)映射成数据库中的记录。
几个玩意:
environments:环境,比如生产环境、测试环境、部署环境等待 default:指定标签中的某一个环境作为默认值 environment:具体每一个环境的定义 transactionManager:事务控制,这里写 JDBCdataSource:数据源 type:类型,这里是 POOLED,连接池property:定义数据库的常规四个属性 mappers:该标签里面存放多个 MyBatis 的映射配置文件mapper:指定每一个映射文件的所在位置几个玩意:
namespace:命名空间,指定该映射文件所指向的接口的全限定路径select:表明这是一条 select 类的 sql 语句 id:指定方法名,唯一,该语句映射完就是映射到一个方法上resultType:方法返回的类型,这里是 Employeeselect 标签之间:写 sql 语句 #{id}:取参数,这里只有一个参数,#{id}、#{arg}、#{abc}都是一个效果的我们把Mybatis的功能架构分为三层:
提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
我们来根据前面的 HelloWorld 案例来梳理 MyBatis 的总体流程。
Created with Raphaël 2.2.0 开始 读取配置文件 myabtis-config.xml 从 myabtis-config.xml 中的 <mappers> 标签找到 EmployeeMapper.xml 在 EmployeeMapper.xml 中找到 getEmpById,请求生成 SQL 语句 注入 id 的值,生成完整的 SQL 语句 去数据库里面玩一圈,执行 SQL 语句 得到结果映射,并进行转换处理,得到最终的 Employee 对象 释放连接资源每个 MyBatis 应用程序主要都是使用SqlSessionFactory实例的,一个SqlSessionFactory实例可以通过SqlSessionFactoryBuilder获得。SqlSessionFactoryBuilder可以从一个xml配置文件或者一个预定义的配置类的实例获得。
用 xml 文件构建 SqlSessionFactory 实例是非常简单的事情。推荐在这个配置中使用类路径资源(classpath resource),但你可以使用任何 Reader 实例,包括用文件路径或 file://开头的 url 创建的实例。MyBatis有一个实用类Resources,它有很多方法,可以方便地从类路径及其它位置加载资源。
触发条件:加载配置文件。
处理过程:
将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。
引入外部配置文件,可以写一些固定的配置,如数据库连接等。
jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mybatis jdbc.username=root jdbc.password=rootmybatis-config.xml
<!-- properties 有两个属性: resource:引用类路径资源 url:引用网络路径或磁盘路径资源 --> <properties resource="jdbc.properties"></properties> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments>可修改MyBatis在运行时的行为方式。
图源:https://blog.csdn.net/fageweiketang/article/details/80767532
下面以 mapUnderscoreToCamelCase(开启自动驼峰命名规则)举例。
类型别名只是Java类型的简称。它仅与 XML 配置有关,只是为了减少完全限定的类名的冗余类型而存在。
这样在 EmployeeMapper.xml 中简化 com.hedon.mybatis.bean.Employee 的写法了。
我们还可以进行批量取别名,经测试,默认别名是类名(首字母大小写都可以)=> 别名不区分大小写。
注意:可能存在子包下也有 Employee 类,导致冲突,运行报错(因为别名已经用过了,想再用到另外一个身上,肯定不行)。
这时候可以用 @Alias 注解来进一步区分。
我们起别名的时候不要和这些重复了,会冲突。
无论是 MyBatis 在预处理语句(PreparedStatement)中 设置一个参数时,还是从结果集中取出一个值时, 都会 用类型处理器将获取的值以合适的方式转换成 Java 类型。
插件是MyBatis提供的一个非常强大的机制,我们可以通过插件来修改 MyBatis 的一些核心行为。插件通过动态代理机制,可以介入四大对象的任何一个方法的执行。
四大对象:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)ParameterHandler (getParameterObject, setParameters)ResultSetHandler (handleResultSets, handleOutputParameters)StatementHandler (prepare, parameterize, batch, update, query)MyBatis可以配置多种环境,比如开发、测试和生 产环境需要有不同的配置。
每种环境使用一个environment标签进行配置并指 定唯一标识符。
可以通过environments标签中的default属性指定一个环境的标识符来快速的切换环境。
指定当前环境的唯一标识。
type:
JDBC —— JdbcTransactionFactory
使用了 JDBC 的提交和回滚设置,依赖于从数 据源得到的连接来管理事务范围。
MANAGED —— ManagedTransactionFactory
不提交或回滚一个连接、让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。
自定义
实现 TransactionFactory 接口,type=全类名/ 别名。
type:
UNPOOLED —— UnpooledDataSourceFactory
不使用连接池。
POOLED —— PooledDataSourceFactory
使用连接池。
JNDI
在 EJB 或应用服务器这类容器中查找指定的数据源。
自定义
实现 DataSourceFactory 接口,定义数据源的获取方式。
MyBatis 可以根据不同的数据库厂商执行不同的语句。
DB_VENDOR:
使用 MyBatis 提供的 VendorDatabaseIdProvider 解析数据库厂商标识。也可以实现 DatabaseIdProvider 接口来自定义。
name
数据库厂商标识。
value
为标识起一个别名,方便SQL语句使用 databaseId 属性引用。
1、如果没有配置databaseIdProvider标签,那么databaseId=null;
2、如果配置了databaseIdProvider标签,使用标签配置的name去匹 配数据库信息,匹配上设置databaseId=配置指定的值,否则依旧为 null;
3、如果databaseId不为null,他只会找到配置databaseId的sql语句;
4、MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的所有语句。如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。
将 sql 映射文件注册到全局配置中。
<insert> 标签中有 2 个属性:
useGeneratedKeys:默认为 false,设置为 true 的话就可以取到自增的主键;keyProperty:指明这个自增得到的主键要放在哪个属性上 <insert id="addEmp" parameterType="com.hedon.mybatis.bean.Employee" useGeneratedKeys="true" keyProperty="id"> insert into tbl_employee(last_name,gender,email) values (#{lastName},#{gender},#{email}) </insert> EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); Employee employee = new Employee(); employee.setLastName("AAAAABC"); employee.setGender("男"); employee.setEmail("!234"); System.out.println("添加前 ID:"+employee.getId()); //添加前 ID:null mapper.addEmp(employee); System.out.println("添加后 ID:"+employee.getId()); //添加后 ID:10 # 补充 原生 JDBC 的 java.sql.Statement 中有 getGeneratedKeys() 方法可以获取。结果:
{gender=1, last_name=hedon, id=1, email=171725713@qq.com}这个时候问题就来了,Map 中的 value 已经解决了,那 key 是什么呢?这里就需要用到 @MapKey 来指定用 Employee 中的哪一个属性来作为 key。
@MapKey("id") Map<String,Employee> getEmployeesMap(); <select id="getEmployeesMap" resultType="com.hedon.mybatis.bean.Employee"> select * from tbl_employee </select>结果:
{1=Employee{id=1, lastName='hedon', gender=1, email='171725713@qq.com'}, 9=Employee{id=9, lastName='AAAAABC', gender=?, email='!234'}, 10=Employee{id=10, lastName='AAAAABC', gender=?, email='!234'}}单个参数的时候 MyBatis 不会做特殊处理,直接 #{随意值} 就可以取出参数的值。
原因分析:
面对多个参数的时候,MyBatis 会做特殊处理。它会将多个参数封装成一个 map。这个 map 有以下规则:
key:param1,param2,……,paramN 或者 arg0,arg1,arg2,……argnvalue:参数值所谓命名参数也就是自己来指定上述 map 的 key,而不用 MyBatis 的默认规则。
首先要在 Mapper 接口类方法传参中加入 @Param 注解:
public interface EmployeeMapper { Employee getEmpByIdAndName(@Param("id") Integer id, @Param("lastName") String lastName); }这样就可以直接指定 key 了:
<select id="getEmpByIdAndName" resultType="com.hedon.mybatis.bean.Employee"> select * from tbl_employee where id = #{id} and last_name = #{lastName} </select>如果是传对象的话,可以直接用 #{属性名} 来取出对象中对应的属性值。
public class Employee { private Integer id; private String lastName; private String gender; private String email; } <update id="updateEmp" parameterType="com.hedon.mybatis.bean.Employee"> update tbl_employee set last_name = #{lastName}, gender=#{gender},email=#{email} where id = #{id} </update>一个思考题:下面的传参该如何取值(根据 id 和 lastName 来查询)?
Employee getEmp(Integer id, Employee employee);因为都没有加 @Param,所以会被 MyBatis 自动封装到 Map 中,且 key 为 param1,param2,……,paramn。但是因为 Employee 是一个对象,所以我们要取出 employee 里面的 lastName 的话,只需要用 “.” 就可以了。综上,如下:
select * from tbl_employee where id = #{param1} and last_name = #{param2.lastName}如果没有现成的 POJO,那么我们可以传 map,然后用 #{key} 取出 map 中的值。
<select id="getEmpByMap" resultType="com.hedon.mybatis.bean.Employee"> select * from tbl_employee where id = #{id} and last_name = #{lastName} </select>测试:
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); Map<String,Object> map = new HashMap<>(); map.put("id",1); map.put("lastName","hedon"); Employee empByMap = mapper.getEmpByMap(map); System.out.println(empByMap); //Employee{id=1, lastName='hedon', gender=1, email='171725713@qq.com'}每次都要封装一个 map 的话就太麻烦了,我们可以直接封装成一个类,这个类我们一般称为 DTO(Data Transfer Object),是专门用来做数据传输的,原理如上,不赘述。
集合类的话 MyBatis 也会进行特殊处理,也是把传入的 list 或 array 存入 map 中,但是这个时候 key 就有特殊规定了:
参数keyConnectionconnectionListlistArrayarray如下:
# 接口方法 Employee getEmployeesByIds(List<\Integer> ids) # Mapper 映射文件 我们现在要取出 ids 中的第一个值 select * from tbl_employee where id = #{list[0]}待补。
最大的区别就在于 填充 的方式不同。我们可以直接举个例子来看看它输出的 SQL 语句的不同:
<select id="getEmpByMap" resultType="com.hedon.mybatis.bean.Employee"> select * from tbl_employee where id = ${id} and last_name = #{lastName} </select>以上我们采用 $ 来取出 id 值,用 # 来取出 lastName 的值。运行 getEmpByMap() 方法,查看 SQL 语句:
我们可以清楚的看到:
$ 取出来的值会直接放到 SQL 语句中,也就是传统的拼接方式。# 取出来的值不会直接放到 SQL 语句中,SQL 语句会在要填充值的地方用 ? 来占位,然后再把 # 取出来的值赋值进去。 # SQL 注入攻击 $ 取出来的值直接放到 SQL 语句中,也就是下面这种方式 method(String id){ String sql = "select * from tbl_employee where id = '" + id + "'"; } 如果 id = “' or 1=1 or id='” 那么拼接起来就是 select * from tbl_employee where id = '' or 1=1 or id='' 里面有一个 1=1,又用的是 or,所以会取出所有的 employee 的信息,这样就会造成数据泄露。 # 什么时候用 $ ? 在`分表查询`的时候,如下按照年份进行分表,那么我们就需要这么查询: select * from ${year}_salary where xxx; 数据库表名是不能用 #{} 来取的,又因为原生的 JDBC 也不支持占位符的方式,所以我们都只能用拼接的方式来进行分表查询。在用 # 进行取值的时候,还可以规定参数的一些规则,如:
javaTypejdbcTypemode:存储过程numericScaleresultMaptypeHandlerjdbcTypeNameexpression(未来准备支持的功能) # 重点记录一下 jdbcType 在我们数据为 null 的时候,有些数据库是不能识别 MyBatis 对 null 的默认处理的。比如 Oracle,它会报错。 因为 MyBatis 默认会将 null 的数据设置为 JdbcType.OTHER 类型的,这个类型 Orcle 不能识别,而 MySQL 可以识别。 这里有两种解决途径: ① 主动指定 null 类型的处理,如:#{email,jdbcType=NULL} ② 在 MyBatis 全局配置文件中设置:<setting name="jdbcTypeForNull" value="NULL"></setting>在 MyBatis 的全局配置的 setting 中有一个属性 autoMappingBehavior,它的值是 PARTIAL,默认开启自动映射的功能。唯一的要求是列名和 javaBean 属性名一致。
如果我们将 autoMappingBehavior设置为 null,就会关闭自动映射。
在前面的基础上,如果我们的 POJO 属性命名符合驼峰命名法,那我们也可以开启自动驼峰命名规则映射功能,即将 mapUnderscoreToCamelCase 设置为 true。
法一:用 association 表示级联查询
property:Employee 类中的属性值javaType:property 是哪种类型的(不可省略) <resultMap id="mapWithDept" type="com.hedon.mybatis.bean.Employee"> <id column="id" property="id"/> <result column="last_name" property="lastName"/> <result column="gender" property="gender"/> <result column="email" property="email"/> <association property="department" javaType="com.hedon.mybatis.bean.Department"> <id column="id" property="id"/> <result column="dept_name" property="departmentName"/> </association> </resultMap> <select id="getEmpWithDept" resultMap="mapWithDept"> select e.id,e.last_name,e.email,e.gender,e.d_id,d.dept_name from tbl_employee e, tbl_dept d where e.d_id = d.id and e.id = #{id} </select>法二:用 “.” 表示级联查询
<resultMap id="mapWithDept" type="com.hedon.mybatis.bean.Employee"> <id column="id" property="id"/> <result column="last_name" property="lastName"/> <result column="gender" property="gender"/> <result column="email" property="email"/> <result column="d_id" property="department.id"/> <result column="dept_name" property="department.departmentName"/> </resultMap> <select id="getEmpWithDept" resultMap="mapWithDept"> select e.id,e.last_name,e.email,e.gender,e.d_id,d.dept_name from tbl_employee e, tbl_dept d where e.d_id = d.id and e.id = #{id} </select>我们还可以先查询 Employee,得到 dId,再用 dId 去查询 Department 的信息:
<resultMap id="mapByStep" type="com.hedon.mybatis.bean.Employee"> <!--第一步:按照员工Id查询出员工信息--> <id column="id" property="id"/> <result column="last_name" property="lastName"/> <result column="gender" property="gender"/> <result column="email" property="email"/> <!--第二步:按照部门ID取查询部门信息 select:表明当前这个属性是根据哪个方法来查询得到的 column:传给另外一个方法的参数 --> <association property="department" select="com.hedon.mybatis.dao.DepartmentMapper.getDeptById" column="d_id"> </association> </resultMap> <select id="getEmployeeWithDeptByStep" resultMap="mapByStep"> select * from tbl_employee where id = #{id} </select>我们每次在查询 Employee 的时候,都把 Department 也一次性查询出来。其实我们可以在需要部门信息的时候再去查询,也就是所谓的延迟加载。
为了实现上述目的,我们需要在分步查询的基础上加上 2 个配置。来到 MyBatis 全局配置文件上:
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> <!-- 开启懒加载支持,默认为 false --> <setting name="lazyLoadingEnabled" value="true"/> <!-- true:属性一次性加载; false:按需加载 --> <setting name="aggressiveLazyLoading" value="false"/> </settings>测试:
当我们要拿到部门信息的时候,才会继续去查询部门信息:
如上,一个部门对应多个员工,我们现在希望在查询部门的时候将属于这个部门的员工也一并查询出来。
Department getDeptWithEmpsById(Integer id); <resultMap id="deptWithEmpsMap" type="com.hedon.mybatis.bean.Department"> <id column="id" property="id"/> <result column="dept_name" property="departmentName"/> <!-- 集合的话就用 collection, ofType 指定集合里面的类型 --> <collection property="employees" ofType="com.hedon.mybatis.bean.Employee"> <!--在 collection 中定义集合中元素的属性的封装规则--> <id column="id" property="id"/> <result column="last_name" property="lastName"/> <result column="email" property="email"/> <result column="gender" property="gender"/> </collection> </resultMap> <select id="getDeptWithEmpsById" resultMap="deptWithEmpsMap"> select d.id, d.dept_name, e.id, e.last_name, e.gender, e.email from tbl_dept d left join tbl_employee e on d.id = e.d_id where d.id = #{id} </select>第一步:查询部门信息
第二步:根据部门ID去查询员工信息
Department getDeptWithEmpsByIdByStep(Integer id); <resultMap id="byStepMap" type="com.hedon.mybatis.bean.Department"> <id column="id" property="id"/> <result column="dept_name" property="departmentName"/> <!--第2步:根据部门ID查询员工信息--> <collection property="employees" select="com.hedon.mybatis.dao.EmployeeMapperPlus.getEmpsByDeptId" column="id"/> </resultMap> <!--第1步:查询部分信息--> <select id="getDeptWithEmpsByIdByStep" resultMap="byStepMap"> select d.id,d.dept_name from tbl_dept d where id = #{id} </select>这里需要在 EmployeeMapperPlus 中加一个 getEmpsByDeptId 方法:
List<Employee> getEmpsByDeptId(Integer id); <select id="getEmpsByDeptId" resultType="com.hedon.mybatis.bean.Employee"> select * from tbl_employee where d_id = #{id} </select>这样就完成一对多的分步查询了。
在前面在 MyBatis 全局配置文件 mybatis-config.xml 中设置了懒加载的基础上,一对多也会自动懒加载:
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> <!-- 开启懒加载支持 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- true:属性一次性加载; false:按需加载 --> <setting name="aggressiveLazyLoading" value="false"/> </settings>如果在分步查询的时候需要传多个值,那就需要将多个值封装到 map 里面来传递,如:
coloum="{key1=column1,key2=column2}"MyBatis 可以使用 discriminator 鉴别器来判断某列的值,然后根据某列的值改变封装行为。
Employee getEmpWithDiscriminator(Integer id); <!-- 实现功能: ① 如果查出的是女生(gender=0),就把部门信息查出来,否则不查询; ② 如果查出的是男生(gender=1),就把 last_name 这一列的值赋值给 email --> <resultMap id="MyEmpWithDiscriminator" type="com.hedon.mybatis.bean.Employee"> <id column="id" property="id"/> <result column="gender" property="gender"/> <result column="email" property="email"/> <result column="last_name" property="lastName"/> <!--鉴别器 column: 指定判定的列名 javaType: 列值对应的类型 --> <discriminator javaType="String" column="gender"> <!--女生--> <case value="0" resultType="com.hedon.mybatis.bean.Employee"> <!--如果是女生,就查询部门信息--> <association property="department" select="com.hedon.mybatis.dao.DepartmentMapper.getDeptById" column="d_id"> </association> </case> <!--男生--> <case value="1" resultType="com.hedon.mybatis.bean.Employee"> <id column="id" property="id"/> <result column="gender" property="gender"/> <!--不查部门信息,并把 last_name 的值赋值给 email--> <result column="last_name" property="email"/> <result column="last_name" property="lastName"/> </case> </discriminator> </resultMap> <select id="getEmpWithDiscriminator" resultMap="MyEmpWithDiscriminator"> select * from tbl_employee where id = #{id} </select>查询一个男生:
@Test public void test5() throws IOException{ InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in); SqlSession sqlSession = sqlSessionFactory.openSession(); try{ EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); Employee emp = mapper.getEmpWithDiscriminator(1); System.out.println(emp); }finally { sqlSession.close(); } in.close(); }查询一个女生:
Employee emp = mapper.getEmpWithDiscriminator(10);访问对象属性: person.name
调用方法:person.getName()
调用静态属性:@java.lang.Math@PI
调用静态方法:@java.util.UUID@randomUUID()
调用构造方法:new com.heon.bean.Employee(“name”).name
算术运算符:+ - * / %
逻辑运算符:in,not in,>,>=,>,>=,==,!=
访问集合伪属性:
类型伪属性伪属性对应的 java 方法List、Set、Mapsize、iSEmptyList/Set/Map.size(),List/Set/Map.isEmpty()List、SetiteratorList.iterator()、Set.iterator()Mapkeys、valuesMap.keySet()、Map.values()Iteratornext、hasNextIterator.next()、Iterator.hasNext()测试:带了 id 和 email
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); Employee employee = new Employee(); employee.setId(1); employee.setEmail("1111"); List<Employee> empsByCondition = mapper.getEmpsByCondition(employee); System.out.println(empsByCondition);测试:只带 lastName
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); Employee employee = new Employee(); employee.setLastName("hedon"); List<Employee> empsByCondition = mapper.getEmpsByCondition(employee); System.out.println(empsByCondition);由上可以看到我们实现了 携带哪个字段就按这个字段来查询 的功能。
上面我们在拼接条件的时候,为了防止 where 后面没有字段而导致 SQL 语法错误我们就加上了 where 1=1。这是可以解决问题的,但是似乎不太优雅。MyBatis 提供了一个新的标签 where 可以帮助我们解决这个问题。
<!--查询员工:携带哪个字段就按这个字段来查询--> <select id="getEmpsByCondition" resultType="com.hedon.bean.Employee"> SELECT * FROM tbl_employee <!--where 会自动帮我们把前面多余的 and 和 or 去掉--> <where> <if test="id!=null"> AND id = #{id} </if> <if test="lastName!=null and lastName.trim()!=''"> AND last_name like #{lastName} </if> <if test="email!=null and email.trim()!=''"> AND email = #{email} </if> <if test="gender==0 or gender==1"> AND gender = #{gender} </if> </where> </select>上面的 where 有一个问题,它只能去掉 前面的 AND。如果 AND 是写在后面的话,如:
<!--查询员工:携带哪个字段就按这个字段来查询--> <select id="getEmpsByCondition" resultType="com.hedon.bean.Employee"> SELECT * FROM tbl_employee <!--where 会自动帮我们把前面多余的 and 和 or 去掉--> <where> <if test="id!=null"> id = #{id} AND </if> <if test="lastName!=null and lastName.trim()!=''"> last_name like #{lastName} AND </if> <if test="email!=null and email.trim()!=''"> email = #{email} AND </if> <if test="gender==0 or gender==1"> gender = #{gender} </if> </where> </select>那么当 gender 属性为 null 的话,SQL 语句最后就会多出一个 AND 而报错。
如果要解决这个问题,一个思路就是老老实实把 AND 写在前面,另外一个思路就是可以使用 trim 标签,它有 4 个属性:
prefix:前缀。给 trim 标签拼串后的整个字符串加一个前缀。prefixOverrides:前缀覆盖。去掉整个字符串前面多余的字符,如 AND,ORsuffix:后缀。给 trim 标签拼串后的整个字符串加一个后置。suffixOverrides:后缀覆盖。去掉整个字符串前面多余的字符,如 AND,OR这样不管是在前面加 AND 还是在后面加 AND,我们都有办法应对了。
<!--查询员工:携带哪个字段就按这个字段来查询--> <select id="getEmpsByCondition" resultType="com.hedon.bean.Employee"> SELECT * FROM tbl_employee <trim prefix="where" prefixOverrides="AND" suffixOverrides="AND"> <if test="id!=null"> id = #{id} AND </if> <if test="lastName!=null and lastName.trim()!=''"> last_name like #{lastName} AND </if> <if test="email!=null and email.trim()!=''"> email = #{email} AND </if> <if test="gender==0 or gender==1"> gender = #{gender} </if> </trim> </select>前面的三个标签都是把所有的条件都拼接起来,如果你只带了 id,就那按 id 查询,如果你 id 和 lastName 一起带了,那就按这两个条件来查询。下面的这个 choose 标签,其实是类似于我们的 switch-case 语句,只选择一个属性进行查询。
<select id="getEmpsByCondition" resultType="com.hedon.bean.Employee"> SELECT * FROM tbl_employee <where> <choose> <when test="id!=null"> id = #{id} </when> <when test="lastName!=null and lastName.trim()!=''"> last_name = #{lastName} </when> <when test="email!=null and email.trim()!=''"> email = #{email} </when> <otherwise> gender = 1 </otherwise> </choose> </where> </select>测试:只带 lastName
测试:同时带 id 和 lastName
测试:都不带
前面都是在封装查询条件,而 set 标签是用来封装我们的修改条件的。
void updateEmp(Employee employee); <update id="updateEmp" parameterType="com.hedon.bean.Employee"> UPDATE tbl_employee <set> <if test="lastName!=null and lastName.trim()!=''"> last_name = #{lastName}, </if> <if test="email!=null and email.trim()!=''"> email = #{email}, </if> <if test="gender==0 or gender ==1"> gender = #{gender} </if> </set> WHERE id = #{id} </update>测试:
employee.setId(1); employee.setLastName("lalalal"); employeeMapper.updateEmp(employee);也可以使用 trim 来代替,也可以配置自动去掉拼接后多出的逗号:
<update id="updateEmp" parameterType="com.hedon.bean.Employee"> UPDATE tbl_employee <trim prefix="set" suffixOverrides=","> <if test="lastName!=null and lastName.trim()!=''"> last_name = #{lastName}, </if> <if test="email!=null and email.trim()!=''"> email = #{email}, </if> <if test="gender==0 or gender ==1"> gender = #{gender} </if> </trim> WHERE id = #{id} </update>我们可以用 foreach 标签来遍历传进来的集合参数:
collection:指定要遍历的集合,可以有 3 种值,collection,list,maplist 类型的参数会特殊处理封装在 map 中,map 的 key 就叫 list
item:指当前遍历出的元素赋值给特定的变量
separator:每个元素之间的分隔符
open:遍历出所有结果后拼接一个开始的字符,如果集合为空的话是不会拼的
close:遍历出所有结果后拼接一个结束的字符,如果集合为空的话是不会拼的
index:索引。
遍历 list 的时候 index 就是索引,item 就是当前值遍历 map 的时候 index 表示的是 map 的key,item 表示的是值#{变量名}:取出变量的值
上述可以实现批量插入 Employee,但是如果传进来的 list 是空的话,就会报错。
这种方式需要开启 MySQL 的多语句执行:allowMultiQueries=true
<dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?allowMultiQueries=true"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource>bind 可以将一个 OGNL 表达式的值绑定到一个变量中,方便后面使用。
List<Employee> getEmpsByLastName(String lastName); <select id="getEmpsByLastName" resultType="com.hedon.bean.Employee"> <bind name="_lastName" value="'%'+lastName+'%'"/> SELECT * FROM tbl_employee WHERE last_name LIKE #{_lastName} </select> List<Employee> emps = mapper.getEmpsByLastName("h");比如上面,我们希望进行模糊查询,并且不希望在传参的时候加上 %%,但是如果我们想用
<select id="getEmpsByLastName" resultType="com.hedon.bean.Employee"> SELECT * FROM tbl_employee WHERE last_name LIKE '%#{_lastName}%' </select>来完成目的是不成功的,#{} 是不支持拼接的。
虽然我们可以用 ${} 来实现:
<select id="getEmpsByLastName" resultType="com.hedon.bean.Employee"> SELECT * FROM tbl_employee WHERE last_name LIKE '%${_lastName}%' </select>但是这样就可能会有 SQL 注入攻击的风险。
所以我们就可以用 bind 来进行绑定,用 _lastName 来替代 %lastName%,从而实现模糊查询。
sql 标签用来抽取可重用的字段,方便后面引用,也支持动态判断。
<sql id="basicColumn"> id,name,gender,address </sql> <seletc id="getUsersInfo"> select <include refid="basicColumn"/> from tbl_employee </seletc>MyBatis 会内置两个参数,任何时候都可以取出来进行一下判断和操作。
_parameter:代表整个参数。
单个参数:_parameter 就是这个参数多个参数:参数会被封装为一个 map,_parameter 就是代表这个 map_databaseId:如果配置了 databaseIdProvicder 标签,_dataBaseId 就是代表当前数据库的别名。
MyBatis 系统中默认定义了两级缓存。
与数据库痛一次会话期间(同一个 SqlSession)查询到的数据会放在本地缓存汇总中。以后如果需要获取相同的数据,直接从缓存中拿,没必 要再去查询数据库。
在 mybatis3.1 之后,可以配置本地缓存的作用域. 在 mybatis.xml 中配置:
二级缓存是基于 namespace 级别的缓存,一个 namespace 对应一个二级缓存。
默认不开启,需要手动开启。
MyBatis 提供二级缓存的接口以及实现,缓存实现要求 POJO 实现 Serializable 接口。
二级缓存在 SqlSession 关闭或提交之后才会生效。
开启全局二级缓存
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> <!-- 开启二级缓存 --> <setting name="cacheEnabled" value="true"/> </settings>去 Mapper.xml 配置二级缓存
<cache/>可以再设置相关属性
eviction:缓存回收策略
LRU – 最近最少使用的:移除最长时间不被使用的对象。(默认)FIFO – 先进先出:按对象进入缓存的顺序来移除它们。SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。flushInterval:刷新间隔,单位毫秒
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。size:引用数目,正整数
代表缓存最多可以存储多少个对象,太大容易导致内存溢出。readOnly:只读
true:只读缓存。会给所有调用者返回缓存对象的相同实例。因为这些对象不能被修改。这提供了很重要的性能优势。
false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些, 但是安全,因此默认是 false。
缓存的相关设置
全局 setting 的 cacheEnable:配置二级缓存的开关。一级缓存一直是打开的。
select 标签的 useCache 属性:配置这个 select 是否使用二级缓存。一级缓存一直是使用的。
sql 标签的 flushCache 属性:增删改默认flushCache=true。sql执行以后,会同时清空一级和二级缓存。查询默认flushCache=false。
sqlSession.clearCache():只能用来清空一级缓存。
当在某一个作用域 (一级缓存Session/二级缓存 Namespaces) 进行了 C/U/D 操作后,默认该作用域下所 有 select 中的缓存将被clear。
先让 Employee 继承 Serializable 接口:
public class Employee implements Serializable {在全局配置文件 mybatis-config.xml 中开启二级缓存
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> <!-- 开启二级缓存 --> <setting name="cacheEnabled" value="true"/> </settings>在 SQL 映射文件中开始二级缓存支持
<cache eviction="LRU" readOnly="false"></cache>测试
@Test public void test2() throws IOException{ InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in); SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); EmployeeMapper employeeMapper1 = sqlSession1.getMapper(EmployeeMapper.class); EmployeeMapper employeeMapper2 = sqlSession2.getMapper(EmployeeMapper.class); Employee emp1 = employeeMapper1.getEmpById(1); System.out.println(emp1); sqlSession1.close();//必须关闭第一个sqlSession,数据才会从一级缓存转移到二级缓存 Employee emp2 = employeeMapper2.getEmpById(1); System.out.println(emp2); sqlSession2.close(); in.close(); }SSM高级整理
Mybatis Generator 的基本使用
MyBatis Generator 拓展:支持多表联合操作