Mybatis框架
第一章
1.三层架构mvc(web开发)
web应用中mvc架构模式。m是数据,v是视图,c是控制器
c控制器:接受请求,调用service对象,显示请求处理结果。可以使用servlet作为控制器。
v视图:现在使用jsp,html,css,js,显示请求处理结果,把m中的数据显示出来
m数据:来自数据库mysql,来自文件。
2.mvc作用
(1)可以实现解耦合
(2)让mvc各负其责,
(3)使系统扩展性好,便于维护
3.三层架构(非web开发)
1.界面层(视图层):接受用户的请求,调用service,显示请求的处理结果。包含jsp,html,servlet等对象。对应的包controller.
2.业务逻辑层:处理业务逻辑,使用算法处理数据,把数据返回给界面层。对应的包是service,和包中的很多的XXXService类。
3.持久层(数据库访问层):访问数据库,或读取文件。对应的包是dao,dao包中有很多StudentDao…
4.三层架构请求的处理流程
用户发起请求–> 界面层–>业务逻辑层–>持久层–>数据库
5.三层架构的优势
1结构清晰、耦合度低
2,可维护性高,可扩展性高
3,利于开发任务同步进行, 容易适应需求变化
6.三层模式结构及框架
一层对应一个框架
(1)界面层–》springMVC框架
(2)业务层–》spring框架
(3)持久层–》MyBatis框架
7.什么是框架?
框架是一个半成品,已经对基础的代码进行了封装并提供相应的API,开发者在使用框架是直接调用封装好的api可以省去很多代码编写,从而提高工作效率和开发速度。
框架是一种经过校验、具有一定功能的半成品软件
经过校验:指框架本身经过测试,且框架自身所具有的功能已经实现
具有一定功能:指框架可以完成特定的功能,不同的框架功能不同。
半成品软件:指框架自身是一个软件,但是该软件无法直接运行,需要配合其他的程序才可以完成指定的工作。
**框架的工作模式:**开发工程师建立在框架的基础之上完成开发者完成部分加框架自身完成部分组成一个完整的产品。
第二章
一.简介
1.什么是 MyBatis?
1.MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。
2.MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
3.MyBatis 可以通过**简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)**为数据库中的记录。
4.MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了[google code](https://baike.baidu.com/item/google code/2346604),并且改名为MyBatis 。
5.iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access
2.为什么要用MyBatis??
1.方便
2.帮助程序员将数据存入数据库中
3.传统的JDBC太复杂。技术没有高低之分
3.MyBatis的特点
1.简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
2.灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
3.解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
4.提供映射标签,支持对象与数据库的orm字段关系映射
5.提供对象关系映射标签,支持对象关系组建维护
6.提供xml标签,支持编写动态sql。
二.MyBatis的基本使用
1.如何搭建一个MyBatis环境?
1.1搭建数据库
#创建一个mybatis数据库 CREATE DATABASE `mybatis`; USE `mybatis`; #创建一个表 CREATE TABLE `user`( `id` INT(20) NOT NULL PRIMARY KEY, `name` VARCHAR(30)DEFAULT NULL, `pwd` VARCHAR(30) DEFAULT NULL )ENGINE=INNODB CHARSET=utf8; INSERT INTO `user`(`id`,`name`,`pwd`) VALUES(1,"刘家堡","125634"), (2,"黄诗敏","123456"), (3,"程大锋","458745"); SELECT * FROM `user`;
1.2maven项目建立
(1)首先创建一个普通的maven项目,不需要导入模板,直接跳过,进入项目后要注意,检查idea中setting设置,看看里面的maven是否配置的自己的maven仓库,如果不是,改成自己的。
(2)把创建的maven项目中的src目录删除,把这个maven当做一个父工程,可以建立很多模块,进行学习。
(3)导入依赖
要使用 MyBatis, 只需将 mybatis-x.x.x.jar 文件置于类路径(classpath)中即可。
如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>x.x.x</version> </dependency>
并且导入mysql的驱动,junit.
<!--导入依赖--> <dependencies> <!--数据库的驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.35</version> </dependency> <!-- Mybatis的依赖jar包--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <!--junit的依赖jar包--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> <scope>test</scope> </dependency> </dependencies>
1.3创建模块
(1)在maven中创建一个子模块,他可以共用富工程的依赖jar包
(2)编写mybatis核心配置文件,在resources资源目录下,配置一个mybatis-config.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"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false &serverTimezone=UTC&useUnicode=true&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="188207236"/> </dataSource> </environment> </environments> </configuration>
(3)编写一个工具类,我们建一个utils包,以后方便把工具类都写入其中。然后创建一个MybatisUtils工具类。通过文件输入流,把资源包下的mybatis-config.xml文件读取出来,并且创建一个sqlSessionFactory对象,(注意:创建SQLSessionFactory对象的三行代码,必须这么写,文档规定,否者会报错!!!),既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。 SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
public class MybatisUtils { //提升SqlSessionFactory的作用域 private static SqlSessionFactory sqlSessionFactory=null; static { try { //必须要这样做,规定的, //(1)获取一个sqlSessionFactory对象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory= new SqlSessionFactoryBuilder().build(inputStream); //上面这三句话是Mybatis使用必要的,不可或缺,少了就会报错。 } catch (IOException e) { e.printStackTrace(); } } /*(2)既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。 SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。 你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。*/ public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); } }
1.4编写代码
把mybatis配置文件编写完后,我就就要开始编写实体类(pojo),dao接口,以及接口的实现类。
(1)编写实体类的代码,注意类中的成员要和你所编写数据库中的变量一一对应,否者容易报错。
//实体类代码 public class User { private int id; private String name; private String pwd; public User(){}; public User(int id,String name,String pwd){ this.id=id; this.name=name; this.pwd=pwd; } public int getId(){ return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", pwd='" + pwd + '\'' + '}'; } }
(2)编写一个user接口
public interface UserDao { List<User> getUserList(); }
(3)编写UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.qyh.dao.UserDao"> <select id="getUserList" resultType="com.qyh.pojo.User"> select * from mybatis.user </select> </mapper>
(4)测试类
@Test public void testGetUserList(){ //获取Session SqlSession session = MybatisUtils.getSqlSession(); UserDao mapper = session.getMapper(UserDao.class); List<User> userList = mapper.getUserList(); for (User user:userList) { System.out.println(user); } session.close(); }
(5)在mybatis-config.xml配置
<!--指定其他mapper文件的位置:目的是为了找到文件中的SQL语句 注意:resource="mapper文件的路径,使用/ 分割路径" 一个mapper指定一个mapper文件 --> <mappers> <mapper resource="com/qyh/dao/UserMapper.xml"/> </mappers>
注意:maven创建项目会屏蔽一些资源,所以在pom.xml中要配置如下代码
<!--资源插件,防止maven过滤资源,处理xml文件--> <build> <resources> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> </build>
提示 对命名空间的一点补充
在之前版本的 MyBatis 中,**命名空间(Namespaces)**的作用并不大,是可选的。 但现在,随着命名空间越发重要,你必须指定命名空间。
命名空间的作用有两个,一个是利用更长的全限定名来将不同的语句隔离开来,同时也实现了你上面见到的接口绑定。就算你觉得暂时用不到接口绑定,你也应该遵循这里的规定,以防哪天你改变了主意。 长远来看,只要将命名空间置于合适的 Java 包命名空间之中,你的代码会变得更加整洁,也有利于你更方便地使用 MyBatis。
**命名解析:**为了减少输入量,MyBatis 对所有具有名称的配置元素(包括语句,结果映射,缓存等)使用了如下的命名解析规则。
- 全限定名(比如 “com.mypackage.MyMapper.selectAllThings)将被直接用于查找及使用。
- 短名称(比如 “selectAllThings”)如果全局唯一也可以作为一个单独的引用。 如果不唯一,有两个或两个以上的相同名称(比如 “com.foo.selectAllThings” 和 “com.bar.selectAllThings”),那么使用时就会产生“短名称不唯一”的错误,这种情况下就必须使用全限定名。
三.mapper代理
3.1 mybatis提供代理:
mapper代理的方式使得程序员只需要写dao接口,dao接口实现则由mybatis自动生成代理对象。
3.2使用DAO开发方式存在的问题
1、dao的实现类中存在重复代码,整个mybatis操作的过程代码模板重复(先创建sqlsession、调用sqlsession的方法、关闭sqlsession)
2、dao的实现类中存在硬编码,调用sqlsession方法时将statement的id硬编码。
3.3使用mapper代理开发规范
要想让mybatis自动创建dao接口实现类的代理对象,必须遵循一些规则:
1、 mapper.xml中namespace指定为mapper接口的全限定名
<?xmlversion="1.0"encoding="UTF-8"?><mappernamespace=“cn.itcast.mybatis.mapper.UserMapper”>
此步骤目的:通过mapper.xml和mapper.java进行关联。
2、mapper.xml中statement的id就是mapper.java中的方法名
3、mapper.xml中statement的parameterType和mapper.java中的方法输入参数类型一致
4、mapper.xml中statement的resultType和mapper.java中方法返回值类型一致.
3.4mybatis的映射文件(UserMapper.xml)
这里我们对mapper映射文件的命名方式使用mybatis的建议:表+Mapper.xml
namespace制定为mapper接口的全限定名
3.5mapper代理的演变
dao接口
User selectUserById(Integer id);
dao接口的实现
@Override public User selectUserById(int id) { SqlSession sqlSession = MyBatisUtil.getSqlSession(); String sqlId="com.qyh.dao.UserDao.selectUserById"; User user= sqlSession.selectOne(sqlId, id); sqlSession.close(); return user; }
mapper.xml文件
<mapper namespace="com.qyh.dao.UserDao">
<select id="selectUserById" resultType="com.qyh.pojo.User">
select * from user where id = #{userId}
</select>
</mapper>
test测试类
@Test
public void testSelectById() {
//(1)首先获取sqlSession对象
SqlSession session = MyBatisUtil.getSqlSession();
//(2)指定sqlID,id:=namespace+'.'+<select>标签的id属性。
String sqlId = "com.qyh.dao.UserDao.selectUserById";
//(3)执行sql方法
User user = session.selectOne(sqlId, 5);
System.out.println("查询结果:" + user);
//(4)关闭SqlSession对象
session.close();
}
mybatis自动创建dao接口实现类的代理,session.getMapper(UserDao.class)(反射机制)不需要手动实现接口。
@Test
public void testSelectById() {
//(1)首先获取sqlSession对象
SqlSession session = MyBatisUtil.getSqlSession();
UserDao mapper = session.getMapper(UserDao.class);
//(3)执行sql方法
User user = mapper.selectUserById(10);
System.out.println("查询结果:" + user);
//(4)关闭SqlSession对象
session.close();
}
第三章
一.理解参数
1.1parameterType:
parameterType:指定dao形参的类型 这个属性可以使用java类型的全限定名称或mybatis定义的别名 mybatis执行sql语句: select * from user where id = ? “?”是占位符,使用jdbc中的preparedStatement执行sql PreparedStatement pst =conn.preparedStatement(sql语句) 然后给?赋值: pst.setInt(5); java类型的全限定名称:parameterType="java.lang.Integer" mybatis定义的别名:parameterType="int" mybatis通过反射机制可以获取dao接口的类型,可以不写
1.2dao方法接口中有多个简单类型参数
当有多个简单类型参数时,要是用**@Param命名参数**,这个注解是mybatis提供的。List selectUserByNameOrPwd(@Param(“myName”) String name,@Param(“myPwd”) String pwd);
mapper文件
/* * 多个简单类型的参数 * 如果这样直接写会报错: * List<User> selectUserByNameOrPwd(String name,String pwd); * ### Cause: org.apache.ibatis.binding.BindingException: * Parameter 'name' not found. Available parameters are [arg1, arg0, param1, param2] * * 当有多个简单类型参数时,要是用@Param命名参数,这个注解是mybatis提供的 * List<User> selectUserByNameOrPwd(@Param(value ="myName") String name, * @Param(value = "myPwd") String pwd); * * 里面的value可以省略 * List<User> selectUserByNameOrPwd(@Param("myName") String name, * @Param("myPwd") String pwd); * * 当使用了@Param时,在mapper要按如下方法使用: * #{myName},#{myPwd} * <select id="selectUserByNameOrPwd" resultType="com.qyh.pojo.User"> select * from user where name=#{myName} or pwd=#{myPwd} </select> * * * */
1.3参数以对象的形式
传入的对象是可以任意对象,在mapper文件中配置参数时,占位符中的参数是对象属性,mybatis会自动调佣getXXX方法。
int updateUser(User user);
<update id="updateUser"> update user set name=#{name},pwd=#{pwd} where id=#{id} </update>
@Test public void updateUser(){ SqlSession session = MyBatisUtil.getSqlSession(); UserDao mapper = session.getMapper(UserDao.class); User user = new User(); user.setId(9); user.setName("刘福河"); user.setPwd("101010"); int i = mapper.updateUser(user); session.commit(); if (i>0){ System.out.println("用户信息修改成"); }else{ System.out.println("用户信息修改失败!"); } session.close(); }
注意:**当进行数据的修改删除操作时,**一定要记得手动提交事务,否者操作无法成功,还有一个方法就是在工具类中,获取sqlSession对象方法时,传入参数 session = sqlSessionFactory.openSession(true)该方法可以自动自动提交事务,不需要手动。
/** * 这是一个工具类,目的是为了创建sqlSession对象 */ public class MyBatisUtil { private static InputStream inputStream = null; private static SqlSessionFactory sqlSessionFactory=null; static { //(1)定义变量来获取主配置文件名字 String resource = "mybatis-config.xml"; try { //(2)读取主配置文件,通过Resources.getResourceAsStream获取一个流。 inputStream = Resources.getResourceAsStream(resource); //(3)通过SqlSessionFactoryBuilder().build(inputStream)来创建SQLSessionFactory对象。 sqlSessionFactory= new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } //创建获取sqlSession对象 public static SqlSession getSqlSession(){ SqlSession session=null; if(sqlSessionFactory!=null){ // session = sqlSessionFactory.openSession(true);自动提交事务 session = sqlSessionFactory.openSession();//手动提交事务 } return session; } }
1.4resultType返回一个简单类型
Integer countUser();
<select id="countUser" resultType="java.lang.Integer">
select count(*) from user
</select>
@Test
public void testCountUser() {
SqlSession session = MyBatisUtil.getSqlSession();
UserDao mapper = session.getMapper(UserDao.class);
Integer integer = mapper.countUser();
System.out.println(integer);
session.close();
}
1.5resultType返回一个map
dao接口返回是一个map,sql语句最多只能获取一条记录,多余一行会报错。
第四章
1.万能map
假设,我们的实体类中,或数据库中的表,字段或者参数过多时,应当考虑使用map。
int addUser(Map<String,Object> map);
<insert id="addUser" parameterType="map"> insert into mybatis.user (id,name,pwd) values (#{userId},#{userName},#{userPwd}) </insert>
@Test public void testAddUser(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserDao mapper = sqlSession.getMapper(UserDao.class); Map<String, Object> map = new HashMap<String, Object>(); map.put("userId",12); map.put("userName","马大哈"); map.put("userPwd","145632"); int i = mapper.addUser(map); if (i>0){ System.out.println("信息插入成功"); }else{ System.out.println("信息插入失败"); } //记得要提交事务 sqlSession.commit(); sqlSession.close(); }
注意:
map传递参数,直接在sql中取出key即可
对象传递参数,直接在sql中取对象的属性即可
只有一个基本类型参数的情况下,可以直接在sql中取。
多个参数可以用map或者注解
2.模糊查询
要注意sql注入问题。
List<User> getUserLike(String value);
<select id="getUserLike" resultType="com.qyh.pojo.User"> select * from mybatis.user where name like #{value } </select>
@Test public void getUserLike(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserDao mapper = sqlSession.getMapper(UserDao.class); List<User> like = mapper.getUserLike("%迎%"); for (User user:like){ System.out.println(user); } sqlSession.close(); }
第五章 配置解析
1.核心配置(mybatis-config.xml)
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下: configuration(配置) properties(属性) settings(设置) typeAliases(类型别名) typeHandlers(类型处理器) objectFactory(对象工厂) plugins(插件) environments(环境配置) environment(环境变量) transactionManager(事务管理器) dataSource(数据源) databaseIdProvider(数据库厂商标识) mappers(映射器)
2.properties属性
在mybatis-config.xml文件中这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置
<dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="188207236"/> </dataSource>
对里面的数据源进行优化,采用外部文件读取,db.properties书写连接数据库的相关配置内容。
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
username=root
password=188207236
然后对mybatis-config.xml文件进行修改为如下,注意引入外部文件时,一定要记得书写引入外部资源的标签。而且必须放在标签的第一位,因为文件的标签有先后顺序的,颠倒会产生错误,无法执行。
<properties resource="db.properties"/>
<!--配置数据源:创建connection对象-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url"
value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
3.给实体类取别名
可以不需要写全限定类名,在resultType中直接写别名即可。
<typeAliases>
<typeAlias type="com.qyh.pojo.User" alias="User"/>
</typeAliases>
<mapper namespace="com.qyh.dao.UserMapper">
<select id="selectUserAll" resultType="User">
select * from mybatis.user
</select>
</mapper>
4.设置(setting)
<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>
5.映射器(mapper)
既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:///
形式的 URL),或类名和包名等
方式一:(推荐)
<!--指定其他mapper文件的位置:目的是为了找到文件中的SQL语句 注意:resource="mapper文件的路径,使用/ 分割路径" 一个mapper指定一个mapper文件 --> <mappers> <mapper resource="com/qyh/dao/UserMapper.xml"/> </mappers> </configuration>
方式二:
<!-- 使用映射器接口实现类的完全限定类名 --> <mappers> <mapper class="org.mybatis.builder.AuthorMapper"/> <mapper class="org.mybatis.builder.BlogMapper"/> <mapper class="org.mybatis.builder.PostMapper"/> </mappers>
方式三:
<!-- 将包内的映射器接口实现全部注册为映射器 --> <mappers> <package name="org.mybatis.builder"/> </mappers>
注意:方法一、方法二的使用需要注意以下几点,否者会报错,无法实现接口的注册。
(1)接口名称和mapper文件必须同名
(2)接口名称和mapper文件必须在同一个包下
6.生命周期
SqlSessionFactoryBuilder
(1)这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。
(2)因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例,使用单例模式或者静态单例模式。
SqlSession
(1)SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
(2)绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。
(3)用完要关闭(非常重要!!!)
第六章
1.如何去解决属性名和数据库中字段名不一致的问题?
采用结果集映射的方法 resultMap.
CREATE TABLE `user`( `id` INT(20) NOT NULL PRIMARY KEY, `name` VARCHAR(30)DEFAULT NULL, `pwd` VARCHAR(30) DEFAULT NULL )ENGINE=INNODB CHARSET=utf8;
`
public class User { private int id; private String name; private String password; }
<mapper namespace="com.qyh.dao.UserMapper"> <!--结果集映射--> <resultMap id="UserMap" type="com.qyh.pojo.User"> <!--column代表数据库中的字段 property代表实体类中的属性--> <result column="id" property="id"/> <result column="name" property="name"/> <result column="pwd" property="password"/> </resultMap> <select id="selectUserById" parameterType="_int" resultMap="UserMap"> select * from mybatis.user where id=#{id} </select> </mapper>
2.日志
(1)MyBatis 内置日志工厂会基于运行时检测信息选择日志委托实现。它会(按上面罗列的顺序)使用第一个查找到的实现。
(2)当没有找到这些实现时,将会禁用日志功能。
(3)如果你的应用部署在一个类路径已经包含 Commons Logging 的环境中,而你又想使用其它日志实现,你可以通过在 MyBatis 配置文件 mybatis-config.xml 里面添加一项 setting 来选择其它日志实现。注意setting的位置顺序。默认日志,不需要导包的,其他的需要导包。
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
3.分页操作
语法:mysql的索引是从0开始,5代表一次显示五条数据,数据库中的0号就会显示第一个数据。
SELECT * FROM USER LIMIT 0,5;
List<User> getUserByLimit(Map<String,Integer> map)
<!--分页-->
<select id="getUserByLimit" parameterType="map" resultType="User" >
select * from user limit #{startIndex},#{pageSize}
</select>
里面设置map的键值对 map.put(“startIndex”,4); map.put(“pageSize”,6),要和mapper文件中,利用占位符(形如:#{XXXXX})设置的参数保持一致,不可以随意put一个键名。
@Test
public void selectUserByLimit(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Integer> map = new HashMap<String, Integer>();
map.put("startIndex",4);
map.put("pageSize",6);
List<User> userByLimit = mapper.getUserByLimit(map);
for (User user : userByLimit) {
System.out.println(user);
}
sqlSession.close();
}
4.注解开发
(1)使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
(2)选择何种方式来配置映射,以及认为是否应该要统一映射语句定义的形式,完全取决于你和你的团队。 换句话说,永远不要拘泥于一种方式,你可以很轻松的在基于注解和 XML 的语句映射方式间自由移植和切换。
public interface BlogMapper {
@Select("SELECT * FROM blog WHERE id = #{id}")
Blog selectBlog(int id);
}
(3)在核心配置文件mybatis-config.xml中绑定接口。
(4)本质是反射机制,代理
5.mybatis执行流程
6.@Param()注解
(1)基本类型的参数或String类型,需要加上
(2)引用类型不需要
(3)只有一个基本类型的参数,可以忽略,但建议加上
(4)在mapper.xml中书写的#{XXX},里面的XXX就是@Param(“XXX”)的设定的属性值。
List<User> selectUserByNameOrPwd(@Param("myName") Stringname, @Param("myPwd") String pwd);
第七章
1.Lombok的使用
这个第三方的工具是用来简化对实体类的操作,可以不需要写那么多get,set等方法。
(1)在idae中,找到setting->plugins搜索lombok,如果没有就通过浏览器打开,并且下载安装。
(2)在maven仓库中下载依赖,并且把依赖导入pom.xml中。
<dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.18</version> </dependency> </dependencies>
(3)在实体类中添加注解。
原先的实体类
package com.qyh.pojo;
//(1)首先先写实体类,然后再写dao层,在里面写dao接口
public class User {
private int id;
private String name;
private String password;
public User() {
}
public User(int id, String name, String pwd) {
this.id = id;
this.name = name;
this.password = pwd;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return password;
}
public void setPwd(String pwd) {
this.password = pwd;
}
@Override
public String toString() {
return "用户实体类{" +
"序号=" + id +
", 用户名='" + name + '\'' +
", 密码='" +password+ '\'' +
'}';
}
}
现在使用lombok注解,只要这样即可
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String password;
}
@Getter and @Setter
@FieldNameConstants
@ToString
@EqualsAndHashCode
@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@val
@var
experimental @var
@UtilityClass
(不太建议使用:了解即可)Lombok是一个很不错的Java库,它可以让你在少写代码的同时耍耍酷,简单的几个注解,就可以干掉一大片模板代码。但是,所有的源代码很多时候是用来阅读的,只有很少的时间是用来执行的(你可以细品这句话)。一年以前,我和大多数人都认为Lombok的出现会让Java的编码体验会更好,并极力的在我的团队中推荐使用Lombok。一年以后,我开始对此产生顾虑,尤其是在我准备为开源的博客系统Una-Boot升级Java版本时,我才意识到Lombok自己掉入了一个戏法陷阱。在我进一步分析其源代码并理解相关注解的工作原理后,发现我并不需要使用一个非标准的第三方库将Java转换为一个精巧而酷炫的语言。引入Lombok让我的项目一时爽,但一时爽的代价是随着项目推进,技术债务开始累积。接下来,我将用几个大家耳熟能详的场景,重演自己是如何掉入Lombok的戏法陷阱。
2.复杂查询环境搭建
何为多对一?例如多个学生,对应一个老师
对于学生而言,【关联】 多个学生,关联一个老师【多对一】
对于老师而言,【集合】一个老师,有很多学生【一对多】
2.1创建表
CREATE TABLE `teacher`(
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO teacher(`id`,`name`)VALUES(1,"刘德华");
CREATE TABLE `student`(
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10)DEFAULT NULL,
PRIMARY KEY(`id`),
FOREIGN KEY(`tid`)REFERENCES `teacher`(id)
)ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO student(`id`,`name`,`tid`) VALUE(1,"刘明书",1);
INSERT INTO student (`id`,`name`,`tid`)VALUE(2,"黄隋杰",1);
INSERT INTO student (`id`,`name`,`tid`) VALUE(3,"张思德",1);
INSERT INTO student (`id`,`name`,`tid`) VALUE(4,"刘明福",1);
INSERT INTO student (`id`,`name`,`tid`)VALUE(5,"邱福天",1);
INSERT INTO student (`id`,`name`,`tid`)VALUE(6,"肖世海",1);
SELECT * FROM student;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5Qras9vT-1617329120313)(C:\Users\86188\AppData\Roaming\Typora\typora-user-images\image-20210328105143842.png)]
2.2环境搭建流程
(1)在pojo包下书写teacher,student实体类
package com.qyh.pojo;
public class Student {
private int id;
private String name;
//学生关联一个老师
private Teacher teacher;
public Student() {
}
public Student(int id, String name, Teacher teacher) {
this.id = id;
this.name = name;
this.teacher = teacher;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", teacher=" + teacher +
'}';
}
}
package com.qyh.pojo;
public class Teacher {
private int id;
private String name;
public Teacher() {
}
public Teacher(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Teacher{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
(2)在utlis下编写或SqlSession的工具类
package com.qyh.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 MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static{
String resources="mybatis-config.xml";
try {
InputStream resourceAsStream = Resources.getResourceAsStream(resources);
sqlSessionFactory= new SqlSessionFactoryBuilder().build(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
(3)配置mybatis-confid.xml和db.properties文件
<?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>
<properties resource="db.properties"/>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!-- <typeAliases>-->
<!-- <typeAlias alias="User" type="com.qyh.pojo.User"/>-->
<!-- </typeAliases>-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!--配置数据源:创建connection对象-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url"
value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!--指定其他mapper文件的位置:目的是为了找到文件中的SQL语句
注意:resource="mapper文件的路径,使用/ 分割路径"
一个mapper指定一个mapper文件
-->
<mappers>
<mapper resource="Mapper\TeacherMapper.xml"/>
</mappers>
</configuration>
数据库的连接配置
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
username=用户名
password=密码
(4)在dao包下编写接口
package com.qyh.dao;
import com.qyh.pojo.Teacher;
public interface TeacherMapper {
Teacher getTeacherById(int id);
}
(5)写接口对应的mapper文件
<?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.qyh.dao.TeacherMapper">
<select id="getTeacherById" resultType="com.qyh.pojo.Teacher">
select * from teacher where id = #{teacherId}
</select>
</mapper>
(6)返回主配置文件中,绑定mapper文件的路径。注意一定要书写正确的文件路径,否者会报错。
<!--指定其他mapper文件的位置:目的是为了找到文件中的SQL语句
注意:resource="mapper文件的路径,使用/ 分割路径"
一个mapper指定一个mapper文件
-->
<mappers>
<mapper resource="Mapper\TeacherMapper.xml"/>
</mappers>
(7)在test类中书写测试代码
package com.qyh.dao;
import com.qyh.pojo.Teacher;
import com.qyh.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
public class MyTest01 {
@Test
public void testSelectTeacherById(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacherById(1);
System.out.println(teacher);
sqlSession.close();
}
}
3.多对一[关联]
要实现以下查询结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BBFISv4K-1617329120323)(C:\Users\86188\AppData\Roaming\Typora\typora-user-images\image-20210328124459814.png)]
3.1按照查询嵌套处理
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--思路: (1)查询所有学生的信息 (2)根据查询的学生中的tid,寻找对应的老师信息 --> <mapper namespace="com.qyh.dao.StudentMapper"> <select id="getStudentAll" resultMap="StudentTeacher"> SELECT * FROM student </select> <resultMap id="StudentTeacher" type="com.qyh.pojo.Student"> <result property="id" column="id"/> <result property="name" column="name"/> <!-- 复杂属性需要单独处理 association:对象 collection:集合 --> <association property="teacher" column="tid" javaType="com.qyh.pojo.Teacher" select="getTeacher"/> </resultMap> <select id="getTeacher" resultType="com.qyh.pojo.Teacher"> select * from teacher where id=#{id} </select> </mapper>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0yPF7YEH-1617329120336)(C:\Users\86188\AppData\Roaming\Typora\typora-user-images\image-20210328153250607.png)]
3.2按照结果嵌套处理
<mapper namespace="com.qyh.dao.StudentMapper"> <!--按照结果嵌套--> <select id="getStudentAll2" resultMap="StudentTeacher2"> SELECT s.`id` sid,s.`name` sname,t.`name` tname FROM student s,teacher t WHERE s.`tid`=t.`id`; </select> <resultMap id="StudentTeacher2" type="com.qyh.pojo.Student"> <result property="id" column="sid"/> <result property="name" column="sname"/> <association property="teacher" javaType="com.qyh.pojo.Teacher"> <result property="name" column="tname"/> </association> </resultMap> </mapper>
@Test public void testSelectStudents2(){ SqlSession sqlSession = MyBatisUtils.getSqlSession(); StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); List<Student> studentAll = mapper.getStudentAll2(); for (Student student : studentAll) { System.out.println(student); } //记得要关闭sqlSession sqlSession.close(); }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NyiDVs3I-1617329120341)(C:\Users\86188\AppData\Roaming\Typora\typora-user-images\image-20210328155339529.png)]
4.一对多
一个老师有多个老师
老师的实体类
package com.qyh.pojo;
import java.util.List;
public class Teacher {
private int id;
private String name;
//一个老师有很多学生,所以这里采用集合
private List<Student> students;
public Teacher() {
}
public Teacher(int id, String name, List<Student> students) {
this.id = id;
this.name = name;
this.students = students;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Student> getStudents() {
return students;
}
public void setStudents(List<Student> students) {
this.students = students;
}
@Override
public String toString() {
return "Teacher{" +
"id=" + id +
", name='" + name + '\'' +
", students=" + students +
'}';
}
}
学生的实体类
package com.qyh.pojo;
public class Student {
private int id;
private String name;
private int tid;
public Student() {
}
public Student(int id, String name, int tid) {
this.id = id;
this.name = name;
this.tid = tid;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getTid() {
return tid;
}
public void setTid(int tid) {
this.tid = tid;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", tid=" + tid +
'}';
}
}
要实现如下结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dP4mM7tD-1617329120345)(C:\Users\86188\AppData\Roaming\Typora\typora-user-images\image-20210328162334362.png)]
到teacherMapper.xml进行书写sql
<?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.qyh.dao.TeacherMapper"> <!--结果嵌套查询--> <select id="getTeacher" resultMap="TeacherStudent"> SELECT s.`id` sid ,s.`name` sname,t.`name` tname,t.`id` tid FROM student s,teacher t WHERE s.`tid`=t.`id` and tid=#{tid} </select> <resultMap id="TeacherStudent" type="com.qyh.pojo.Teacher"> <result property="id" column="tid"/> <result property="name" column="tname"/> <!--集合 javaType=""指定的属性的类型。 而集合中的泛型,使用ofType=""获取。 --> <collection property="students" ofType="com.qyh.pojo.Student"> <result property="id" column="sid"/> <result property="name" column="sname"/> <result property="tid" column="tid"/> </collection> </resultMap> </mapper>
测试类
@Test public void getTeacher(){ SqlSession sqlSession = MyBatisUtils.getSqlSession(); TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class); Teacher teachers = mapper.getTeacher(1); System.out.println(teachers); sqlSession.close(); }
第八章 动态sql
1.搭建环境
(1)创建表
CREATE TABLE blog(
`id` VARCHAR(50) NOT NULL COMMENT '博客id',
`title` VARCHAR(100) NOT NULL COMMENT '博客标题',
`author` VARCHAR(30) NOT NULL COMMENT '博客作者',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`views` INT(30) NOT NULL COMMENT '浏览量'
)ENGINE=INNODB DEFAULT CHARSET=utf8
SELECT * FROM blog;
INSERT INTO blog VALUE("188207236","动态Sql问题","马和伟","2020-08-31 00:00:00",1200);
INSERT INTO blog VALUE("188207237","mybatis动态代理","刘大兵","2020-08-31 10:40:15",1980);
(2)编写实体类代码和Utils工具类
package com.qyh.pojo; import java.util.Date; public class Blog { private String id; private String title; private String author; private Date createTime; private int views; public Blog() { } public Blog(String id, String title, String author, Date createTime, int views) { this.id = id; this.title = title; this.author = author; this.createTime = createTime; this.views = views; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } public int getViews() { return views; } public void setViews(int views) { this.views = views; } @Override public String toString() { return "Blog{" + "id='" + id + '\'' + ", title='" + title + '\'' + ", author='" + author + '\'' + ", createTime=" + createTime + ", views=" + views + '}'; } }
package com.qyh.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 MyBatisUtils { private static SqlSessionFactory sqlSessionFactory; static{ String resources="mybatis-config.xml"; try { InputStream resourceAsStream = Resources.getResourceAsStream(resources); sqlSessionFactory= new SqlSessionFactoryBuilder().build(resourceAsStream); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); } }
(3)产生随机id的工具类
package com.qyh.utils; import java.util.UUID; public class getIdUtils { public static String getId(){ return UUID.randomUUID().toString().replaceAll("-",""); } }
(4)测试代码
package com.qyh.dao;
import com.qyh.pojo.Blog;
import com.qyh.utils.MyBatisUtils;
import com.qyh.utils.getIdUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.Date;
public class MyTest {
@Test
public void addBlog(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Blog blog = new Blog();
blog.setId(getIdUtils.getId());
blog.setTitle("MySQL中如何删除数据");
blog.setAuthor("遅咲きの向日葵");
blog.setCreateTime(new Date());
blog.setViews(452);
int i = mapper.addBlog(blog);
if (i>0){
System.out.println("数据添加成功");
}else {
System.out.println("数据添加异常");
}
sqlSession.commit();
sqlSession.close();
}
}
(5)BlogMapper文件
<?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.qyh.dao.BlogMapper">
<insert id="addBlog" parameterType="com.qyh.pojo.Blog">
insert into blog (id,title,author,create_time,views)
values(#{id},#{title},#{author},#{createTime},#{views})
</insert>
<select id="selectBlogIf" parameterType="map" resultType="com.qyh.pojo.Blog">
select * from blog
<if test="title !=null">
and title=#{title}
</if>
<if test="author !=null">
and author=#{author}
</if>
</select>
</mapper>
2.动态sql常用标签
如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
if
使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。比如:
<select id="findActiveBlogWithTitleLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
这条语句提供了可选的查找文本功能。如果不传入 “title”,那么所有处于 “ACTIVE” 状态的 BLOG 都会返回;如果传入了 “title” 参数,那么就会对 “title” 一列进行模糊查找并返回对应的 BLOG 结果(细心的读者可能会发现,“title” 的参数值需要包含查找掩码或通配符字符)。
如果希望通过 “title” 和 “author” 两个参数进行可选搜索该怎么办呢?首先,我想先将语句名称修改成更名副其实的名称;接下来,只需要加入另一个条件即可。
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>
choose、when、otherwise
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
还是上面的例子,但是策略变为:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG(这可能是管理员认为,与其返回大量的无意义随机 Blog,还不如返回一些由管理员精选的 Blog)。
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
trim、where、set
前面几个例子已经方便地解决了一个臭名昭著的动态 SQL 问题。现在回到之前的 “if” 示例,这次我们将 “state = ‘ACTIVE’” 设置成动态条件,看看会发生什么。
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>
如果没有匹配的条件会怎么样?最终这条 SQL 会变成这样:
SELECT * FROM BLOG
WHERE
这会导致查询失败。如果匹配的只是第二个条件又会怎样?这条 SQL 会是这样:
SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’
这个查询也会失败。这个问题不能简单地用条件元素来解决。这个问题是如此的难以解决,以至于解决过的人不会再想碰到这种问题。
MyBatis 有一个简单且适合大多数场景的解决办法。而在其他场景中,可以对其进行自定义以符合需求。而这,只需要一处简单的改动:
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
来看看与 set 元素等价的自定义 trim 元素吧:
<trim prefix="SET" suffixOverrides=",">
...
</trim>
注意,我们覆盖了后缀值设置,并且自定义了前缀值。
foreach
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!
提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
第九章 缓存
(1)【经常查询且不进厂改变的数据使用缓存】
(2)一级缓存是本地缓存
(3)开启显示缓存
<setting name="cacheEnabled" value="true"/>
MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。
默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:
<cache/>
基本上就是这样。这个简单语句的效果如下:
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
提示 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。
这些属性可以通过 cache 元素的属性来修改。比如:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
可用的清除策略有:
LRU
– 最近最少使用:移除最长时间不被使用的对象。FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
默认的清除策略是 LRU。
flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
提示 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。
使用自定义缓存
除了上述自定义缓存的方式,你也可以通过实现你自己的缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为。
<cache type="com.domain.something.MyCustomCache"/>
这个示例展示了如何使用一个自定义的缓存实现。type 属性指定的类必须实现 org.apache.ibatis.cache.Cache 接口,且提供一个接受 String 参数作为 id 的构造器。 这个接口是 MyBatis 框架中许多复杂的接口之一,但是行为却非常简单。
public interface Cache {
String getId();
int getSize();
void putObject(Object key, Object value);
Object getObject(Object key);
boolean hasKey(Object key);
Object removeObject(Object key);
void clear();
}
为了对你的缓存进行配置,只需要简单地在你的缓存实现中添加公有的 JavaBean 属性,然后通过 cache 元素传递属性值,例如,下面的例子将在你的缓存实现上调用一个名为 setCacheFile(String file)
的方法:
<cache type="com.domain.something.MyCustomCache">
<property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>
你可以使用所有简单类型作为 JavaBean 属性的类型,MyBatis 会进行转换。 你也可以使用占位符(如 ${cache.file}
),以便替换成在配置文件属性中定义的值。
从版本 3.4.2 开始,MyBatis 已经支持在所有属性设置完毕之后,调用一个初始化方法。 如果想要使用这个特性,请在你的自定义缓存类里实现 org.apache.ibatis.builder.InitializingObject
接口。
public interface InitializingObject {
void initialize() throws Exception;
}
提示 上一节中对缓存的配置(如清除策略、可读或可读写等),不能应用于自定义缓存。
请注意,缓存的配置和缓存实例会被绑定到 SQL 映射文件的命名空间中。 因此,同一命名空间中的所有语句和缓存将通过命名空间绑定在一起。 每条语句可以自定义与缓存交互的方式,或将它们完全排除于缓存之外,这可以通过在每条语句上使用两个简单属性来达成。 默认情况下,语句会这样来配置:
<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>
鉴于这是默认行为,显然你永远不应该以这样的方式显式配置一条语句。但如果你想改变默认的行为,只需要设置 flushCache 和 useCache 属性。比如,某些情况下你可能希望特定 select 语句的结果排除于缓存之外,或希望一条 select 语句清空缓存。类似地,你可能希望某些 update 语句执行时不要刷新缓存。
cache-ref
回想一下上一节的内容,对某一命名空间的语句,只会使用该命名空间的缓存进行缓存或刷新。 但你可能会想要在多个命名空间中共享相同的缓存配置和实例。要实现这种需求,你可以使用 cache-ref 元素来引用另一个缓存。
<cache-ref namespace="com.someone.application.data.SomeMapper"/>