mybatis
<一>:创建核心配置文件mybatis-config.xml(官方推荐名)
如果一个属性在不只一个地方进行了配置,那么,MyBatis 将按照下面的顺序来加载:
- 首先读取在 properties 元素体内指定的属性。
- 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
- 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。
因此,通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性。
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuratio
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--configuration核心配置文件--><configuration>
<properties resource="db.properties"/> //外部关联和内部属性同时存在时,优先外部关联 <typeAliases>
<!--<typeAlias type="com.whily.pojo.Emp" alias="Emp"/> 限定这个类的别名--> <package name="com.whily.pojo"/><!--将这个包下的类的首字母小写作为别名--> </typeAliases> <!--default-设置默认加载的环境 --> <environments default="development"> <environment id="development">
<transactionManager type="JDBC"/>
<!-- 配置数据源 -->
<dataSource type="POOLED">
<!-- 配置数据源的属性 -->
<property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${uid}"/> <property name="password" value="${pwd}"/> </dataSource> </environment> </environments> <!--每一个Mapper都需要在Mybatis核心配置文件中注册--> <mappers> <mapper resource="EmpMapper.xml"/> </mappers> </configuration>
<二>:创建工具类
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; //sqlSessionFactory--> sqlSession public class MybatisUtils { private static SqlSessionFactory sqlSessionFactory; static { try { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession getSqlSession() { return sqlSessionFactory.openSession(); } }
<三>:创建实体类以及Dao接口
import org.apache.ibatis.type.Alias; @Alias(value = "Emp") //注解别名 public class Emp { private int empid; private String name; private String username; private String password; private int deptid; public Emp(int empid, String name, String username, String password, int deptid) { this.empid = empid; this.name = name; this.username = username; this.password = password; this.deptid = deptid; } public Emp() { } public int getEmpid() { return empid; } public void setEmpid(int empid) { this.empid = empid; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getDeptid() { return deptid; } public void setDeptid(int deptid) { this.deptid = deptid; } @Override public String toString() { return "emp{" + "empid=" + empid + ", name='" + name + '\'' + ", username='" + username + '\'' + ", pwd='" + password + '\'' + ", deptid=" + deptid + '}'; } }
<四>:创建sql语句Mapper.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"> <!--namespace绑定一个对应的Dao/Mapper接口--> <mapper namespace = "com.whily.dao.EmpDao"> <select id="getEmpList" resultType="Emp"> select * from employee; </select> <select id="getEmpByid" parameterType="int" resultType="Emp"> select * from employee where empid=#{id}; </select> <insert id="addEmp" parameterType="Emp" > insert into employee(empid,name,username,password,deptid)values (#{empid},#{name},#{username},#{password},#{deptid}); </insert> <update id="update" parameterType="Emp"> update employee set name=#{helloname},username=#{hellousername},password=#{hellopassword},deptid=#{hellodeptid} where empid=#{helloempid}; </update> <!--万能Mapper--> <delete id="delete" parameterType="map" > delete from employee where empid=#{Helloid}; </delete> <select id="getEmpLike" parameterType="map" resultType="Emp"> select * from employee where name like "%"#{value}"%"; </select> </mapper>
<五>:创建测试类
增删改一定要提交事务
sqlSession.commit();
sqlSession.close();
package com.whily.dao; import com.whily.pojo.Emp; import com.whily.utils.MybatisUtils; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import java.util.HashMap; import java.util.List; import java.util.Map; public class EmpDaoTest { @Test public void test() { //获取SqlSession对象 SqlSession sqlSession = MybatisUtils.getSqlSession(); //执行SQL //方式一:getMapper EmpDao mapper = sqlSession.getMapper(EmpDao.class); List<Emp> empList = mapper.getEmpList(); for (Emp emp : empList) { System.out.println(emp); } System.out.println("******************"); //方式二: List<Emp> emps = sqlSession.selectList("com.whily.dao.EmpDao.getEmpList"); for (Emp emp : emps) { System.out.println(emp); } sqlSession.close(); } @Test public void getEmpByid() { SqlSession sqlSession = MybatisUtils.getSqlSession(); EmpDao mapper = sqlSession.getMapper(EmpDao.class); Emp emp = mapper.getEmpByid(196); System.out.println(emp); sqlSession.close(); } @Test public void addEmp() { SqlSession sqlSession = MybatisUtils.getSqlSession(); EmpDao mapper = sqlSession.getMapper(EmpDao.class); int emps = mapper.addEmp(new Emp(500, "qqq", "hahaha", "1111", 5)); if (emps > 0) { System.out.println("成功"); } sqlSession.commit(); sqlSession.close(); } @Test public void update() { SqlSession sqlSession = MybatisUtils.getSqlSession(); EmpDao mapper = sqlSession.getMapper(EmpDao.class); Map<String, Object> map = new HashMap<String, Object>(); map.put("helloname", "qqq"); map.put("hellousername", "qqq"); map.put("hellopassword", "www"); map.put("hellodeptid", 1); map.put("helloempid", 196); int emp = mapper.update(map); if (emp > 0) { System.out.println("成功"); } sqlSession.commit(); sqlSession.close(); } @Test public void delete() { SqlSession sqlSession = MybatisUtils.getSqlSession(); Map<String, Object> map = new HashMap<String, Object>(); map.put("Helloid", 500); EmpDao mapper = sqlSession.getMapper(EmpDao.class); mapper.delete(map); sqlSession.commit(); sqlSession.close(); } @Test public void getEmpLike() { SqlSession sqlSession = MybatisUtils.getSqlSession(); EmpDao mapper = sqlSession.getMapper(EmpDao.class); List<Emp> emps = mapper.getEmpLike("%qq%"); for (Emp emp : emps) { System.out.println(emp); } sqlSession.close(); } }
六、生命周期
每一个Mapper都是一个业务
生命周期和作用域是至关重要的,因为错误的使用会导致非常严重的并发问题。
SqlSessionFactoryBulider:
- 一旦创建了SqlSessionFactoryBulider,就不需要他了
- 局部变量
SqlSessionFactory:
- 说白了可以想象为数据库连接池;
- SqlSessionFactory一旦被创建就应该在应用的运行期间一直存在,没有任何理由放弃或重新创建
- 因此SqlSessionFactory的最简作用域是应用作用域;
- 最简单的就是单例模式或者静态单例模式。
SqlSession:
- 连接到连接池的一个请求
- SqlSession的实例不是线程安全的,因此不是共享的,所以他的作用域是请求或方法作用域
- 用完之后要赶紧关闭,否则资源被占用!
字段和属性名不一致
- 起别名
7、ResultMap映射:结果集映射
<resultMap id="EmpMap" type="Emp"> <!--column数据库的字段 property实体类的属性--> <result column="empid" property="empid"/> <result column="username" property="username"/> <result column="deptid" property="deptid"/> <result column="name" property="name"/> <result column="password" property="pwd"/> // </resultMap> <select id="getEmpList" resultMap="EmpMap" //对应ResultMap的id值> select * from employee; </select>
- resultMap元素是MyBatis中最重要最强大的元素
- ResultMap的设计思想是,对于简单的语句根本不需要配置显示的结果映射,而对于复杂一点的语句只需要描述他们的关系就行
- ResultMap最优秀的地方在于,虽然你已经对他相当了解了,但根本就不需要显示地用到他们
设置
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
8、日志
8.1 日志工厂
如果一个数据库操作出现了异常,我们需要拍错。日志就是最好的帮手
以前:sout、debug
现在:日志工厂! logImpl 指定Mybatis所用日志的具体表现,未指定时将自动查找。
- SLF4J
- LOG4J (掌握)
- LOG4J2
- JDK_LOGGING
- STDOUT_LOGGING (掌握)
- NO_LOGGING
- COMMONS_LOGGING
具体用使用哪一个日志实现,在设置中设定!
STDOUT_LOGGING
<settings> <setting name="logImpl" value="STDOUT_LOGGING"/> //必须规范书写,按官方书写,不能有空格 </settings>
@Test
public void getEmpLike() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
List<Emp> emps = mapper.getEmpLike("%qq%");
for (Emp emp : emps) {
System.out.println(emp);
}
sqlSession.close();
}
Opening JDBC Connection
Created connection 385337537.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@16f7c8c1]
==> Preparing: select * from employee where name like "%"?"%";
==> Parameters: %qq%(String)
<== Columns: empid, name, username, password, deptid
<== Row: 229, qaaqq, 1111, dsa, 2
<== Row: 231, qqaqq, dsad5, dsa, 7
<== Total: 2
emp{empid=229, name='qaaqq', username='1111', pwd='dsa', deptid=2}
emp{empid=231, name='qqaqq', username='dsad5', pwd='dsa', deptid=7}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@16f7c8c1]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@16f7c8c1]
Returned connection 385337537 to pool.
log4j:
1、导入依赖:
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
2、mybatis-config.xml设置
<settings> <setting name="logImpl" value="LOG4J"/> </settings>
3、创建log4j.properties文件
log4j.rootLogger=DEBUG,console,file #控制台输出的相关设置 log4j.appender.console = org.apache.log4j.ConsoleAppender log4j.appender.console.Target = System.out log4j.appender.console.Threshold=DEBUG log4j.appender.console.layout = org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=[%c]-%m%n #文件输出的相关设置 log4j.appender.file = org.apache.log4j.RollingFileAppender log4j.appender.file.File=./log/whily.log log4j.appender.file.MaxFileSize=10mb log4j.appender.file.Threshold=DEBUG log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n #日志输出级别 log4j.logger.org.mybatis=DEBUG log4j.logger.java.sql=DEBUG log4j.logger.java.sql.Statement=DEBUG log4j.logger.java.sql.ResultSet=DEBUG log4j.logger.java.sql.PreparedStatement=DEBUG
4、测试类
public class EmpDaoTest { static Logger logger = Logger.getLogger(EmpDaoTest.class); @Test public void getEmpLike() {
logger.info("测试:进入getEmpLike方法成功");
SqlSession sqlSession = MybatisUtils.getSqlSession();
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
List<Emp> emps = mapper.getEmpLike("%qq%");
for (Emp emp : emps) {
System.out.println(emp);
}
logger.info("info:进入了testLog4j");
logger.error("error:进入了testLog4j");
logger.debug("debug:进入了testLog4j");
sqlSession.close();
}
}
简单使用:
1.在使用Log4j的类中,导入import org.apache.log4j.Logger;
2.日志对象,参数为当前类的class:static Logger logger = Logger.getLogger(EmpDaoTest.class);
3.日志级别:
logger.info("info:进入了testLog4j");
logger.debug("debug:进入了testLog4j");
logger.error("error:进入了testLog4j");
9、分页
分页可以减少数据的处理量
使用Limit分页:
语法:
select * from tableName limit startIndex,pageSize;
select * from employee limit 3; #[0,n]
9.1:Limit分页
1.接口
分页 List<Emp> getEmpLimit(Map<String,Object> map);
2.Mapper.xml
<select id="getEmpLimit" parameterType="map" resultMap="EmpMap"> select * from employee limit #{i},#{n}; </select>
3.测试
@Test public void getEmpLimit() { SqlSession sqlSession = MybatisUtils.getSqlSession(); EmpDao mapper = sqlSession.getMapper(EmpDao.class); Map<String, Object> map = new HashMap<String, Object>(); map.put("i", 1); map.put("n", 3); List<Emp> empLimit = mapper.getEmpLimit(map); for (Emp emp:empLimit){ System.out.println(emp); } sqlSession.close(); }
9.2:使用RowBounds(不推荐,因为需要使用selectList(),官方放弃了这个方法)
1.接口 :List<Emp> getEmpRowBounds();
2.Mapper.xml
<select id="getEmpLimit" parameterType="map" resultMap="EmpMap"> select * from employee; </select>
3.测试
@Test
public void getEmpRowBounds(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
//RowBounds实现
RowBounds rowBounds = new RowBounds(1,3);
List<Emp> emps = sqlSession.selectList("com.whily.dao.EmpDao.getEmpRowBounds",null,rowBounds);
for (Emp emp:emps){
System.out.println(emp);
}
sqlSession.close();
}
9.3:插件分页
1.到依赖
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.2</version> </dependency>
2.导入插件
<plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <property name="helperDialect" value="mysql"/> <property name="reasonable" value="true"/> </plugin> </plugins>
3.Dao
List<Emp> listByHelper(); //条件查询是需要传入参数
4.Mapper.xml
<select id="listByHelper" resultMap="EmpMap"> select * from employee </select>
sql语句后面不能加“;”
5.测试
@Test public void ss() { SqlSession sqlSession = MybatisUtils.getSqlSession(); EmpDao mapper = sqlSession.getMapper(EmpDao.class); PageHelper.startPage(2, 2); List<Emp> emps = mapper.listByHelper(); PageInfo<Emp> info = new PageInfo<Emp>(emps); emps = info.getList(); for (Emp emp : emps) { System.out.println(emp); } }
10.注解开发
修饰在接口方法上,然后加相应的sql语句 @Select @Select("select * from emp") public List<Emp> listAnn(Emp emp); @Insert @Insert("insert into emp (ename) values (#{ename})") public int saveAnn(Emp emp); @Update @Update("update emp set ename=#{ename} where empno=#{empno}") public int editAnn(Emp emp); @Delete @Delete("delete from emp where empno=#{empno}") public int deleteAnn(Emp emp); @Results @Results(value={ @Result(property = "sal", column = "salary") }) @Select("select * from emp") public List<Emp> listAnn(Emp emp);
#{}和${}:
where role_id = #{roleId};
#{}:传来参数时,sql在解析时会家伙是哪个“ ”,当成字符串来解析,防止sql注入。
${}:传入数据直接显示在生成的sql中,解析式就成了role_id = roleId,运行时会报错。
所以尽可能用#{},除非Mybatis排序时使用order by动态参数时需要使用${}而不是#{}
11、 连表查询
一:多对一映射
1、创建mybatis-config.xml 实体类 接口(Dao或Mapper)
2、Mapeer.xml文件:
1.方式一,不使用连接查询,效率一般:
<resultMap id="StudentTeacher" type="stu"> <result property="stuid" column="stuid" javaType="int"/> <result property="stuName" column="stuName" javaType="string"/> <association property="teacher" column="tid" javaType="tea" select="getTeacher" //连接第二条查询语句 > <id property="tchid" column="tchid" javaType="int"></id> <result property="tchName" column="tchName" javaType="string"></result> </association> </resultMap> <select id="getStudents" resultMap="StudentTeacher"> select * from student </select> <select id="getTeacher" resultType="tea" parameterType="int"> select * from teacher where tchid =#{tchid} ; </select>
2.方式二,使用连接查询:
<select id="getStudents2" resultMap="StudentTeacher2"> select s.stuid sid,s.stuName sname,t.tchid tchid,t.tchName tname from student s ,teacher t where s.tid = t.tchid </select> <resultMap id="StudentTeacher2" type="stu"> <result property="stuid" column="sid"/> <result property="stuName" column="sname"/> <association property="teacher" javaType="tea"> <id property="tchid" column="tchid"/> <result property="tchName" column="tname"/> </association> </resultMap>
二:一对多映射
1.方式一,不使用连接查询:
<select id="getTeacher2" resultMap="TeacherStu2"> select tchid,tchName from teacher where tchid = #{tchid} </select> <resultMap id="TeacherStu2" type="tea"> <result property="tchid" column="tchid"/> <result property="tchName" column="tchName"/> <collection property="students" javaType="ArrayList" ofType="stu" column="tchid"//对应第一条语句的tchid select="getStudentByID"连接第二条语句> <result property="stuid" column="stuid"/> <result property="stuName" column="stuName"/> <result property="tid" column="tid"/> </collection>
<select id="getStudentByID" resultType="stu">
select stuid,stuName,tid from student where tid = #{tchid}
</select>
</resultMap>
2.方式二,使用连接查询:
<select id="getTeacher" resultType="tea" resultMap="TeacherStu"> select t.tchid ,t.tchName,s.stuid,s.stuName,s.tid from student s,teacher t where s.tid = t.tchid and tchid = #{tchid} </select> <resultMap id="TeacherStu" type="tea"> <result property="tchid" column="tchid"/> <result property="tchName" column="tchName"/> <collection property="students" ofType="stu"> <result property="stuid" column="stuid"/> <result property="stuName" column="stuName"/> <result property="tid" column="tid"/> </collection> </resultMap>
12、动态SQL
官网描述:
MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。
例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。
虽然在以前使用动态 SQL 并非一件易事,但正是 MyBatis 提供了可以被用在任意 SQL 映射语句中的强大的动态 SQL 语言得以改进这种情形。
动态 SQL 元素和 JSTL 或基于类似 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多元素需要花时间了解。MyBatis 3 大大精简了元素种类,
现在只需学习原来一半的元素便可。MyBatis 采用功能强大的基于 OGNL 的表达式来淘汰其它大部分元素。
-------------------------------
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
-------------------------------
我们之前写的 SQL 语句都比较简单,如果有比较复杂的业务,我们需要写复杂的 SQL 语句,往往需要拼接,而拼接 SQL ,稍微不注意,由于引号,空格等缺失可能都会导致错误。
那么怎么去解决这个问题呢?这就要使用 mybatis 动态SQL,通过 if, choose, when, otherwise, trim, where, set, foreach等标签,可组合成非常灵活的SQL语句,从而在提高 SQL 语句的准确性的同时,也大大提高了开发人员的效率。
if: 接口为List<Student> queryStuIf(Map map);
<select id="queryStuIf" parameterType="map" resultType="stu"> select * from student where <if test="stuid!=null">stuid=#{stuid}</if> <if test="stuName!=null">and stuName=#{stuName}</if> </select>
完整语句为select * from student where stuid = #{stuid} and stuName = #{stuName},
当stuName为空时select * from student where stuid = #{stuid}
但当stuid为空时语句就成为了select * from student where and stuName = #{stuName},sql语句错误。
如何解决:where
WHERE:接口同上
修改以上sql语句:
<select id="queryStuIf" parameterType="map" resultType="stu"> select * from student where <where> <if test="stuid!=null">stuid=#{stuid}</if> <if test="stuName!=null">and stuName=#{stuName}</if> </where> </select>
public void queryOne(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); HashMap<Object,Object> map = new HashMap<Object, Object>(); map.put("stuid",1); map.put("stuName",null); List<Student> list = mapper.queryStuIf(map); System.out.println(list); sqlSession.close(); }
这个“where” 标签会知道如果他包含的标签中有返回值的话,太插入一个‘where’。此外,如果标签返回的内容是以AND
或 OR开头的话,则会剔除掉。
SET:接口为int update(Map map);
同理:上面的对于查询SQL语句包含where关键字,如果在进行更新操作时,含有set关键词,我们怎么处理
<update id="update" parameterType="map" > update student <set> <if test="hobby!=null">hobby=#{hobby},</if> //注意set与set之间需要加逗号隔开 <if test="stuName">stuName=#{stuName}</if> </set> where stuid = #{stuid} </update>
public void update(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); HashMap<String,Object> map = new HashMap<String, Object>(); map.put("stuName","熊大"); map.put("hobby","dance"); map.put("stuid",1); mapper.update(map); sqlSession.commit(); sqlSession.close(); }
CHOOSE语句:List<Student> queryChoose(Map map);
有时候我们不想用到所有的查询条件,只想选择其中一个,查询条件只有一个满足即可,
使用choose标签可以解决此类问题,类似于java的switch语句
<select id="queryChoose" parameterType="map" resultType="stu"> select * from student <where> <choose> <when test="stuid">stuid=#{stuid}</when> <when test="stuName">stuName=#{stuName}</when> <when test="hobby">hobby#{hobby}</when> <otherwise>and view = #{view}</otherwise> </choose> </where> </select>
public void queryChoose() { SqlSession sqlSession = MybatisUtils.getSqlSession(); StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); HashMap<String, Object> map = new HashMap<String, Object>(); map.put("stuName", "张三"); //map.put("stuid",1); // map.put("hobby","lol"); // map.put("view",1); List<Student> list = mapper.queryChoose(map); System.out.println(list); sqlSession.close();
SQL片段:接口List<Student> queryFra(Map map);
有时候可能某个sql语句用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。
<!--提取片段--> <sql id="if-stuid-stuName"> <if test="stuid!=null">stuid=#{stuid}</if> <if test="stuName!=null">stuName=#{stuName}</if> </sql> <!--使用片段--> <select id="queryFra" parameterType="map" resultType="stu"> select * from student <where> <include refid="if-stuid-stuName"></include> </where> </select>
public void queryFra(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); HashMap<String, Object> map = new HashMap<String, Object>(); //map.put("stuName", "张三"); map.put("stuid",1); List<Student> list=mapper.queryFra(map); System.out.println(list); sqlSession.close(); }
注意:提取片段时,最好基于单表来定义sql片段,提高片段的可重用性;
在sql片段中不要包括where
FOREACH:接口List<Student> queryForeach(Map map);
collection:指定输入对象中的集合属性 item:每次遍历生成的对象 open:开始遍历时的拼接字符串 close:结束时拼接的字符串 separator:遍历对象之间需要拼接的字符串 select * from blog where 1=1 and (id=1 or id=2 or id=3) --> <select id="queryForeach" parameterType="map" resultType="stu"> select * from student <where> <foreach collection="stuids" item="stuid" open="and (" close=")" separator=" or ">stuid=#{stuid}</foreach>//注意空格 </where> </select>
public void queryForeach(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); HashMap map = new HashMap(); List<Integer> ids = new ArrayList<Integer>(); ids.add(1); ids.add(2); ids.add(3); map.put("stuids",ids); List<Student> students = mapper.queryForeach(map); System.out.println(students); sqlSession.close(); }
13、缓存
1.什么是缓存:- 存在内存中的临时数据。
- 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
- 减少和数据库的交互次数,减少系统开销,提高系统效率。
- 经常查询并且不经常改变的数据。
13.1、Mybatis缓存
- MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
- MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
- 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
13.1、一级缓存
一级缓存也叫本地缓存:
-
与数据库同一次会话期间查询到的数据会放在本地缓存中。
-
以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;
@org.junit.Test public void queryOne(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); Student i = mapper.queryOne(1); System.out.println(i); Student j = mapper.queryOne(1); System.out.println(j); System.out.println(i==j); sqlSession.close(); }
一级缓存失效的四种情况:
一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它;
一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求!
1.sqlSession不同:
@org.junit.Test public void queryOne(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
SqlSession sqlSession1=MybatisUtils.getSqlSession(); StudentMapper mapper1 = sqlSession1.getMapper(StudentMapper.class);
Student i = mapper.queryOne(1); System.out.println(i);
Student j = mapper1.queryOne(1); System.out.println(j); System.out.println(i==j); sqlSession.close(); sqlSession1.close(); }
观察结果:发现发送了两条SQL语句!
结论:每个sqlSession中的缓存相互独立
2.sqlSession相同,查询条件不同
3.sqlSession相同,两次查询之间执行了增删改操作!无论改的是哪个对象,增删改会刷新缓存
4.sqlSession相同,手动清除一级缓存
@org.junit.Test public void queryOne(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); Student i = mapper.queryOne(1); System.out.println(i); sqlSession.clearCache();//手动清除缓存 Student j = mapper.queryOne(1); System.out.println(j); System.out.println(i==j); sqlSession.close(); }
一级缓存就是一个map
13.2 、二级缓存
-
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
-
基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
-
工作机制
-
一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
-
如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
-
新的会话查询信息,就可以从二级缓存中获取内容;
-
不同的mapper查出的数据会放在自己对应的缓存(map)中;
使用步骤:
1、开启全局缓存【mybatis-config.xml】
<setting name="cacheEnabled" value="true"/>
2、去每个mapper.xml中配置使用二级缓存,这个配置非常简单;【xxxMapper.xml】
<mapper namespace="com.whily.dao.StudentMapper">
// 在当前Mapper.xml中使用二级缓存 <cache/> //开启二级缓存 <select id="queryOne" resultType="stu"> select * from student where stuid=#{stuid} </select> </mapper>
<cache/> 官方示例=====>查看官方文档 <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,
而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突
3.测试
@org.junit.Test public void queryOne(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); SqlSession sqlSession1 = MybatisUtils.getSqlSession(); StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); StudentMapper mapper1 = sqlSession.getMapper(StudentMapper.class); Student i = mapper.queryOne(1); System.out.println(i); Student j = mapper1.queryOne(1); System.out.println(j); System.out.println(i==j); sqlSession.close(); sqlSession1.close(); }
org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections. [com.whily.dao.StudentMapper]-Cache Hit Ratio [com.whily.dao.StudentMapper]: 0.0 [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection [org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 11902257. [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@b59d31] [com.whily.dao.StudentMapper.queryOne]-==> Preparing: select * from student where stuid=? [com.whily.dao.StudentMapper.queryOne]-==> Parameters: 1(Integer) [com.whily.dao.StudentMapper.queryOne]-<== Total: 1 Student{stuid=1, stuName='熊大', hobby=dance, tid=1} [com.whily.dao.StudentMapper]-Cache Hit Ratio [com.whily.dao.StudentMapper]: 0.0 Student{stuid=1, stuName='熊大', hobby=dance, tid=1} true
4.结论
-
只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据
-
查出的数据都会被默认先放在一级缓存中
-
只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中
5.缓存原理图
13.3、第三方缓存【EHCache】
第三方缓存实现--EhCache: 查看百度百科 Ehcache是一种广泛使用的java分布式缓存,用于通用缓存; 要在应用程序中使用Ehcache,需要引入依赖的jar包 <!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache --> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.1.0</version> </dependency> 在mapper.xml中使用对应的缓存即可 <mapper namespace = “org.acme.FooMapper” > <cache type = “org.mybatis.caches.ehcache.EhcacheCache” /> </mapper> 编写ehcache.xml文件,如果在加载时未找到/ehcache.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"> <!-- diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下: user.home – 用户主目录 user.dir – 用户当前工作目录 java.io.tmpdir – 默认临时文件路径 --> <diskStore path="./tmpdir/Tmp_EhCache"/> <defaultCache eternal="false" maxElementsInMemory="10000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="259200" memoryStoreEvictionPolicy="LRU"/> <cache name="cloud_user" eternal="false" maxElementsInMemory="5000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="1800" memoryStoreEvictionPolicy="LRU"/> <!-- defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。 --> <!-- name:缓存名称。 maxElementsInMemory:缓存最大数目 maxElementsOnDisk:硬盘最大缓存个数。 eternal:对象是否永久有效,一但设置了,timeout将不起作用。 overflowToDisk:是否保存到磁盘,当系统当机时 timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。 timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。 diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false. diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。 diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。 clearOnFlush:内存数量最大时是否清除。 memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。 FIFO,first in first out,这个是大家最熟的,先进先出。 LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。 LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。 --> </ehcache>