2.mybatis
1. 组件
1.1 SqlSessionFactoryBuilder
利用xml或java编码(Configuration)构建SqlSessionFactory
2. 生命周期
(1)SqlSessionFactoryBuilder
作为SqlSessionFactory的构建者,当SqlSessionFactory创建完成,则此对象就没用了。因此它的生命周期应只存在于 方法的局部。
(2)SqlSessionFactory
作用是创建SqlSession(一个会话,类似JDBC的Connection对象),每次要访问数据库时,需要创建一个SqlSession,所以此工厂类对象应存在mybatis应用的整个生命周期中。
它的作用单一,就是创建SqlSession,因此采用单例模式(一个数据库对应一个SqlSessionFactory)
(3)SqlSession
在数据库处理事务过程中有效存在
线程不安全
使用完成后,需要close归还给连接池
(4)Mapper
作用是发送SQL,返回需要的结果。
存活在一个SqlSession事务方法之内,当SelSession销毁时,Mapper也会销毁
3. 一个utils工具类
MybatisUtil.java
package com.xt.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; public class MybatisUtil { //单例工厂对象 private static SqlSessionFactory sqlSessionFactory = null; //类线程锁 private static final Class CLASS_LOCK = MybatisUtil.class; /* * 私有构造 单例 * */ private MybatisUtil() {} /* * 构建工厂 * */ public static SqlSessionFactory initSqlSessionFactory() { String resource = "SqlMapConfig.xml"; InputStream inputStream = null; try { inputStream = Resources.getResourceAsStream(resource); } catch (IOException e) { e.printStackTrace(); } synchronized (CLASS_LOCK) { if(sqlSessionFactory == null) { sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } } return sqlSessionFactory; } /* * 获取一个SqlSession * */ public static SqlSession getSqlSession() { if(sqlSessionFactory == null) { initSqlSessionFactory(); } return sqlSessionFactory.openSession(); } }
4. 配置
基本配置格式:
<?xml version="1.0" encoding="UTF-8"?> <configuration> <properties/> <!--属性--> <settings/> <!--设置--> <typeAliases/> <!--类型命名--> <typeHandlers> <!--类型处理器--> <objectFactory/> <!--对象工厂--> <plugins/> <!--插件--> <environments default="mysql"> <!-- 配置环境 --> <environment id="mysql"> <!--mysql环境配置--> <transactionManager type="JDBC"></transactionManager> <!--配置事务类型--> <!--配置数据源(连接池)--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/contacts?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="xxx"/> </dataSource> </environment> </environments> <!--指定映射配置文件(每个dao独立的配置文件)的位置--> <mappers> <mapper resource="com/xt/dao/UserDao.xml"></mapper> <!-- <mapper class="com.xt.dao.UserDao"></mapper>--> </mappers> </configuration>
4.1 properties
优先级:(1)>(2)>(3)
(1)property子元素
在上面的properties里面设置dirver、url等:
<properties> <!--属性--> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/contacts?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="xxx"/> </properties>
在下面datasource里面取值:
<dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource>
(2)properties配置文件
建一个配置文件:jdbc.properties
driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/contacts?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8 username=root password=xxx
在mybatis的配置文件的properties标签里面引入:然后可在datasource标签里取出来
<properties resource="jdbc.properties" />
(3)程序参数传递
配置文件里面的属性加密,decode解密?????
书上这样写的:修改后的工具类
public static SqlSessionFactory initSqlSessionFactory() { String resource = "SqlMapConfig.xml"; //mybatis主配置文件 InputStream cfgStream = null; //主配置文件输入流 Reader cfgReader = null; //给工厂建造者作为参数使用 InputStream proStream = null; //属性配置文件输入流 Reader proReader = null; //给工厂建造者作为入参使用 Properties properties = null; //接收属性配置文件 try { //读入主配置文件 cfgStream = Resources.getResourceAsStream(resource); cfgReader = new InputStreamReader(cfgStream); //读入属性配置文件 proStream = Resources.getResourceAsStream("jdbc.properties"); proReader = new InputStreamReader(proStream); properties = new Properties(); properties.load(proReader); //属性解密 properties.setProperty("username",decode(properties.getProperty("username"))); properties.setProperty("passoword",decode(properties.getProperty("password"))); } catch (IOException e) { e.printStackTrace(); } synchronized (CLASS_LOCK) { if(sqlSessionFactory == null) { sqlSessionFactory = new SqlSessionFactoryBuilder().build(cfgReader,properties); } } return sqlSessionFactory; }
4.2 settings
4.3 typeAliases
用简短名称代替过长的类全限定名,这个名称可在mybatis上下文中使用
(1)系统定义别名
在 org.apache.ibatis.type.TypeAliasRegistry 定义了这些
(2)自定义
自定义类的别名:使用xx代替很长的全限定名
<typeAliases> <typeAlias alias="xx" type="com.xt.abc.efg.Xx"/> </typeAliases>
包中有很多类时,在各个类上面加注解,然后配置里不写alias,mybatis会自己扫描各个别名:
<typeAliases> <package name="com.xt.abc.efg" /> <package name="com.xt.pack2.abc" /> </typeAliases>
包中类的信息:例
package com.xt.abc.efg
@Alias("classa")
public class ClassA {}
@Alias("classb")
public class ClassB {}
4.4 typeHandler 类型处理器
在预处理语句PreparedStatement中设置一个参数时,或从结果集ResultSet中取出一个值时,都会用注册了的 typeHandler处理
常用:java类型、jdbc类型
typeHandler:将参数从javaType转化为jdbcType,或从数据库取出结果时将jdbcType转化为javaType
(1)系统预定义的
org.apache.ibatis.type.TypeHandlerRegistry
(2)自定义
(3)枚举类型typeHandler
4.5 objectFactory
mybats在构建一个结果返回时,会使用 ObjectFactory 构建POJO
默认的:org.apache.ibatis.reflection.factory.DefaultObjectFactory
4.6 插件
4.7 environments
注册多个数据源,每个数据源包括:数据库源配置、数据库事务配置
(1)数据源
(2)事务
事务由 SqlSession控制(commit、rollback)
4.8 映射
5. 映射器
<?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.xt.dao.UserDao"> <!--配置查询所有--> <select id="findAll" resultType="com.xt.domain.User"> select * from user </select> <select id="findById" parameterType="int" resultType="com.xt.domain.User"> select * from user where id = #{id} </select> <insert id="insertUser" parameterType="com.xt.domain.User" > insert into user values (null,#{username},STR_TO_DATE(#{birthday},'%Y-%m-%d %H:%i:%s'),#{sex},#{address}) </insert> <delete id="deleteUser" parameterType="int" > delete from user where id = #{id} </delete> </mapper>
5.1 select
(1)元素配置
(2)自动映射
java中属性名和数据库中字段名不一致时,可通过数据库的别名形成自动映射
自动映射的设置在 <settings> 中配置
(3)多个入参
①map传递
例:
xml配置:
<select id="findByMap" parameterType="map" resultType="com.xt.domain.User"> select * from user where username like concat('%',#{username},'%') and address like concat('%',#{address},'%') </select>
接口:
public List<User> findByMap(Map<String,String> params);
调用者:
Map<String,String> params = new HashMap(String,String)(); params.put("username","xt"); params.put("address","address1"); mapper.findByMap(params);
②注解
xml配置:
<select id="findByMap" resultType="com.xt.domain.User"> select * from user where username like concat('%',#{username},'%') and address like concat('%',#{address},'%') </select>
接口:
public List<User> findByMap(@Param("username") String name,@Param("address")String addr);
调用者直接传入两个字符串即可
③javaBean
参数很多时,创建一个类,参数作为成员,有getter、setter,在select标签里parameterType就是该类全限定名
(4)resultMap映射结果集
查询返回的结果可能不能完全映射到一个java bean上去,因此定义resultMap接收返回结果集
5.2 insert、update、delete
5.3 参数
(1)参数配置
指定特定类型的处理器、指定数值类型保存的精度
例:
#{age,javaType=int,jdbcType=NUMERIC} //使用系统定义的typeHandler处理
#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler} //使用自定义的typeHandler处理
#{price,javaType=double,jdbcType=NUMERIC,numericScale=2}
(2)存储过程支持
(3)特殊字符替换和处理
# $
5.4 sql元素
5.5 resultMap
5.6 缓存
(1)系统缓存
默认开启一级缓存,在参数和SQL完全一样的情况下,使用同一个SqlSession对象调用同一个Mapper的方法,一般只会执行一次SQL。但在一个SqlSession提交commit后再开一个SqlSession,如果也有缓存的话,它和之前的缓存是相互隔离的,需要重新执行SQL(即使参数、SQL还是一样)
二级缓存可使缓存在SqlSessionFactory层面上能提供给各个SqlSession对象共享
开启二级缓存要求返回的POJO能序列化(实现了Serializable接口),然后在映射xml文件里加上 <cache/>
①全局配置的settings里面加
<settings> <setting name="cacheEnabled" value="true" /> </settings>
②映射xml里面
<cache/>
也可以设置缓存的属性
如果某些语句需要每次调用都执行sql(即个别语句不用缓存),可在其标签内加 useCache flushCache属性
如:useCache默认值为true
<select id="xx" useCache="false" resultType="xxxxx" parameterType="xxx"> sql语句 </select>
(2)自定义缓存
比如用Redis作为缓存
需要实现 org.apache.ibatis.cache.Cache 接口,实现类在 cache标签里通过type属性指定
---
6. 动态SQL
6.1 if
例:模糊查询,传入的username可能传了空,则需要判断入参值然后决定是否拼sql
<select id="findUser" parameterType="string" resultTypeMap="userResultMap"> select user_no,user_name,note from t_user where 1 = 1 <if test="username != null and username != ''"> and user_name like concat('%',#{username},'%') </if> </select>
6.2 choose、when、otherwise
<select id="findUser" parameterType="User" resultTypeMap="userResultMap"> select user_no,user_name,note from t_user where 1 = 1 <choose> <when test="userNo != null and userNo != ''"> AND user_no = #{userNo} </when> <when test="userName != null and userName != ''"> AND user_name like concat('%',#{userName},'%') </when> <otherwise> AND note is not null </otherwise> </choose> </select>
6.3 trim、where、set
(1)where:where里面的元素的条件成立时,生成 where 关键字组成sql(这样就不用写 where 1 = 1了)
而且,如果里面语句开头是 AND、OR,也会去掉(比如这里前面的sex为空,而username不为空,则去掉and),然后组成正确的SQL
<select id="findUser" parameterType="string" resultTypeMap="userResultMap"> select user_no,user_name,note from t_user <where> <if test="sex != null and sex != ''"> sex like concat('%',#{sex},'%') </if> <if test="username != null and username != ''"> and user_name like concat('%',#{username},'%') </if> </where> </select>
(2)trim
trim可以自定义元素的功能
①自定义上面的where元素:
<select id="findUser" parameterType="string" resultTypeMap="userResultMap"> select user_no,user_name,note from t_user <trim prefix="WHERE" prefixOverride="AND | OR"> <if test="sex != null and sex != ''"> sex like concat('%',#{sex},'%') </if> <if test="username != null and username != ''"> and user_name like concat('%',#{username},'%') </if> </trim> </select>
trim里面的子元素不是空的的时候,插入 prefix(即 WHERE);如果这里第一个条件是空的而第二个不是空,则去掉 prefixOverride(即这里的and)形成正确的sql
②自定义下面的set元素
<update id="xx"> update table1 <trim prefix="SET" suffixOverrides=","> <if test="username != null and username != ''">username=#{username},</if> <if test="sex != null and sex != ''">sex=#{sex},</if> <if test="addr != null and addr != ''">address=#{addr},</if> </trim> where id = #{id} </update>
(3)set
上面的where、trim可用来动态拼接条件,而set可用在update更新语句中动态包含要更新的列
<update id="xx"> update table1 <set> <if test="username != null and username != ''">username=#{username},</if> <if test="sex != null and sex != ''">sex=#{sex},</if> <if test="addr != null and addr != ''">address=#{addr},</if> </set> where id = #{id} </update>
6.4 foreach
如果要动态生成 如 select * from xx where field1 in (xxx,xxx,xxx) 这样的语句,调用者可传入数组、List、Set集合对象等
例:
<select id="findByAddrList" resultType="com.xt.domain.User"> select * from user where address in <foreach item="addr" index="i" collection="list" open="(" separator="," close=")"> #{addr} </foreach> </select>
collection:传进来的参数名称
item:循环中当前元素,即值
index:集合下标或键
open、close:
separator:分隔符
另一个问题:如果输入的list或set、数组里面没有数据,则会导致 select * from t where address in () 这样的错误SQL
修改如下:
<select id="findByAddrList" resultType="com.xt.domain.User"> select * from user <where> <if test="list.size()>0"> address in </if> <foreach item="addr" index="i" collection="list" open="(" separator="," close=")"> #{addr} </foreach> </where> </select>
6.5 bind
在OGNL表达式外创建一个变量,并将其绑定到当前的上下文
作用就是简化写法。而且bind是将param视作字符串拼接,能防止注入。
<select id="xx" resultType="xxx"> <bind name="pattern" value="'%' + param + '%'"/> select * from table1 where name like #{pattern} --在上面定义的pattern在这里就用到了 </select>