MyBatis
要多对官方文档进行学习
https://mybatis.org/mybatis-3/zh/index.html
简介
MyBatis
- 持久层框架 Dao Access Objects
- 定制化sql,存储过程,高级映射
- 避免了所有JDBC代码,设置参数,获取结果集,都不用自己手写
- 可以使用XML或注解,来配置和映射原生类型、接口和java的POJO为数据库记录
- POJO(Plain Old Java Objects ,普通老式Java对象)
- 原来叫iBatis
获得MyBatis
GitHub
Maven
持久化
持久化就是将程序的数据在 持久状态 和 瞬时状态 转化的过程
内存:断电即失
数据库(JDBC),IO文件持久化
- 之前的数据存储可能是直接通过IO存储在文本中,但是IO操作十分消耗资源,之后数据库诞生
内存太贵,所以数据要存在能持久存放的硬盘中
持久层
Dao层、service层、Controller层
完成持久化框架的代码块
层的界面十分明显
为什么需要MyBatis
方便
传统的jdbc太复杂,简化、框架、自动化
不用Mybatis也可以、只是更容易上手
优点:
-
简单、小巧、灵活、
-
解除SQL与程序的耦合
- sql和程序分开,直接去改sql就行
-
单元测试
- sql代码分离
-
提供映射标签
-
提供XML标签,支持动态sql
主要是使用的人多
Spring、SpringMVC、SpringBoot
第一个MyBatis程序
思路:
- 环境搭建
- 导入Mybatis
- 编写代码
- 制作工具类utils
- 制作JavaBean
- 制作mapper
- 测试
搭建环境
-
创建数据库
CREATE TABLE `mybatis`.`Untitled` ( `id` int(20) NOT NULL, `name` varchar(30) NULL, `pwd` varchar(30) NULL, PRIMARY KEY (`id`) ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci;
设置engine引擎为InnoDB,和Character字符编码为utf8
-
创建简单的Maven项目,删除src
-
<dependencies> <!--Mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <!--Mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.11</version> </dependency> <!--Junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies>
-
加入依赖文本
-
主要根据文档进行
-
https://mybatis.org/mybatis-3/zh/index.html
-
-
里面创建的子maven项目就不用频繁导包了
-
<parent> <artifactId>Mybatis_Study</artifactId> <groupId>com.haoyun</groupId> <version>1.0-SNAPSHOT</version> </parent>
-
子模块的xml是继承父包的
编写Mybatis配置文件
工厂模式为核心的,构造出实例
-
在resource文件下创建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.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL-true&useUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <!--映射--> <mappers> <mapper resource="UserMapper.xml"/> </mappers> </configuration>
-
配置SSL安全连接,使用useUnicode编码,usecharacter字符编码utf8
创建实例SqlSession
-
官方给了一些代码,
-
String resource = "org/mybatis/example/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
-
把它封装为我们之前的工具类,utils,就是input配置文件,使用他的方法创建实例
-
/*通过工厂方法生产Sqlsession实例*/ public class MybatisUtils { private static final SqlSessionFactory sqlSessionFactory; static { /*获取sqlSessionFactory对象*/ String resource = "src\\main\\resources\\mybatis-config.xml"; InputStream inputStream = null; try { inputStream = Resources.getResourceAsStream(resource); } catch (IOException e) { e.printStackTrace(); } sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } /*获取sqlSession实例*/ public static SqlSession getSqlSession() { return sqlSessionFactory.openSession(); } }
创建Bean
使用Lombok
这些方法就已经很够用了
NotNull还能自行判断空指针
自动创建十分方便
添加maven依赖文本就行
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
制作接口
Mapper
只要建立一个接口,不用实现,通过xml配置文件
语句可以通过XML定义也可以通过注解定义,Mybatis提供的所有特性都可以通过XML映射语言来实现
public interface UserDao {
List<User> getUserList();
}
之前编写Dao层,需要写一个实现这个接口的类,每个方法里有不同的对数据进行操作的jdbc语句,现在不用写实现类,通过编写配置文件,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.haoyun.dao.UserDao">
<!--对应mapper的方法作为id,和返回类型,这里最好和接口中定义泛型一致-->
<select id="getUserList" resultType="com.haoyun.POJO.User">
select * from mybatis.user
</select>
</mapper>
整体结构
测试
编写规范,测试的是哪个位置的文件就写到哪,名字加个Test
public class UserDaoTest {
@Test
public void test(){
/*创建sqlSession实例*/
SqlSession sqlSession = MybatisUtils.getSqlSession();
/*第一种方法:执行sql,获取sql的对象,调用它定义的发方法*/
UserDao userDao = sqlSession.getMapper(UserDao.class);
List<User> userList = userDao.getUserList();
/*第二种发方法:*/
//List<User> userList = sqlSession.selectList("com.haoyun.dao.UserDao.getUserList");
for (User user : userList) {
System.out.println(user);
}
/*关闭*/
sqlSession.close();
}
}
介绍了两种方法,第二种方法认识一下就好
总结:
-
-
Builder只是用来创建SqlSessionFactory,读配置文件的,所以放到了static代码块中,只要加载一次就好
-
SqlSessionFactory,创建一次就一直存在,工厂模式,用来创建sqlsession实例,最简单的单例模式
-
sqlSession不是线程安全的
-
使用后要进行关闭,要求放到finally块中
-
所以测试的代码应该这样改
-
public class UserDaoTest { @Test public void test() { /*创建sqlSession实例*/ SqlSession sqlSession = MybatisUtils.getSqlSession(); try { /*第一种方法:执行sql,获取sql的对象,调用它定义的发方法*/ UserDao userDao = sqlSession.getMapper(UserDao.class); List<User> userList = userDao.getUserList(); /*第二种发方法:*/ //List<User> userList = sqlSession.selectList("com.haoyun.dao.UserDao.getUserList"); for (User user : userList) { System.out.println(user); } } catch (Exception e) { e.printStackTrace(); } finally { /*关闭*/ sqlSession.close(); } } }
-
Mybatis常见错误
Mapper
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-54j8VAq0-1597893958664)(…/…/…/AppData/Roaming/Typora/typora-user-images/image-20200815211128717.png)]
Dao层接口的方法需要通过创建对应Mapper完成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.haoyun.dao.UserDao">
<select id="getUserList" resultType="com.haoyun.POJO.User">
select * from mybatis.user
</select>
</mapper>
这是对UserDao接口,制作的Mapper.xml,对getUserList方法执行这条sql语句,返回类型为User
这个mapper制作好后需要到mybatis-config.xml去注册mapper
<?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.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL-true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/haoyun/dao/UserMapper.xml"/>
</mappers>
</configuration>
选择resource的位置
解决了这个问题会出现第二个问题
Maven导出的问题,Maven是注重规定的,xml文件就要放在resource文件夹下,要不然无法导出
target文件夹下没有UserMapper.xml
就会报错
可能是这个文件出错
没有找到这个资源文件
查看target后发现的确没有
这时候有两种方法
- 添加Maven依赖文本,让maven支持其他文件夹也能导出xml文件
- 放到resource文件夹下,改好路径
Maven导出问题
-
添加依赖文本,搜索maven导出
-
https://www.cnblogs.com/yuqiliu/p/12059614.html
-
<build> <resources> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </build>
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8qW54oVK-1597893958675)(https://gitee.com/haoyunlwh/Typoraimage/raw/master/img/image-20200815214037843.png)]
-
运行成功,第一条说这个url太老了,建议换新,这种方法也没什么人用了
-
-
放入resource文件夹下
-
更改mybatis-config.xml中的mapper路径
-
<property name="driver" value="com.mysql.cj.jdbc.Driver"/> <mappers> <mapper resource="UserMapper.xml"/> </mappers>
-
顺便把第一个问题解决
-
运行成功,所以最好把配置文件放在resource文件夹下
-
CRUD
- 增、删、改、查
- 四个功能的流程都是一样的
- 添加Mapper接口的方法
- 编写Mapper.xml配置文件映射
- 编写对应功能的sql语句,给入参数
- 调用
Insert
-
添加Mapper接口方法
-
/*添加一个用户*/ public int addUser(User user);
-
在Mapper.xml下添加对应语句
-
在命名空间下添加
-
<insert id="addUser" parameterType="com.haoyun.POJO.User" > insert into mybatis.user (id,name,pwd) values (#{id},#{name},#{pwd}); </insert>
-
使用insert标签,参数类型,使用 #{} 编写参数引用
-
测试
-
多加了一个插件GenerateAllSetter,自动生成Set方法
-
@Test public void addUserTest(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = new User(); user.setId(5); user.setName("haoyun"); user.setPwd("1234"); mapper.addUser(user); /*提交事务,不加提交不了*/ sqlSession.commit(); sqlSession.close(); }
-
-
之后都是类似的操作
Delete
-
/*删除一个用户*/ public int deleteUser(int id);
-
<delete id="deleteUser" parameterType="int"> delete from mybatis.user where id = #{id} </delete>
Update
-
/*修改一个用户,根据Id,修改名字*/ public int updateUser(User user);
-
<update id="updateUser" parameterType="com.haoyun.POJO.User"> update mybatis.user set name = #{name},pwd = #{pwd} where id = #{id} </update>
Select
-
/*根据id查找用户*/ public User getUserById(int id);
-
<select id="getUserById" parameterType="int" resultType="com.haoyun.POJO.User"> select * from mybatis.user where id = #{id} </select>
总结
- 选择好参数类型和返回值类型
- 编写在namespace命名空间标签下
- mybatis-config.xml要编写mapper.xml的映射
- sql语句接收参数使用**#{}**
- 增删改操作要加commit提交
使用Map传参
-
不同点就是配置时的不同点和传参时不同
-
/*使用map传递参数插入*/ public int addUserMap(Map<String,Object> map);
-
<insert id="addUserMap" parameterType="com.haoyun.POJO.User" > insert into mybatis.user (id,name,pwd) values (#{User_ID},#{User_name},#{User_pwd}); </insert>
-
@Test public void addUserMapTest(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); Map<String, Object> map = new HashMap<String, Object>(); map.put("User_ID",4); map.put("User_name","map"); map.put("User_pwd","12345"); int i = mapper.addUserMap(map); sqlSession.commit(); sqlSession.close(); }
-
Map传递参数,直接在Sql中取出key即可 parameterType = “map”
-
对象传递参数,直接在sql中取对象的属性即可 parameterType = “Object”
-
只有一个基本类型参数情况下,可以直接在sql中取到,一个参数,都不用加parameterType
-
多个参数使用Map,或注解
-
拓展:模糊查询
-
通过传递字符串中带通配符 实现模糊查询,或者sql拼接
-
/*模糊查询*/ List<User> USER_LIST(String value);
-
<select id="USER_LIST" resultType="com.haoyun.POJO.User"> select * from user where name like "%"#{value}"%" </select>
-
@Test public void selectTest(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> users = mapper.USER_LIST("张"); for (User user : users) { System.out.println(user); } sqlSession.close(); }
-
这里就是只有一个参数连parameterType都不用写
-
List<User> users = mapper.USER_LIST("%张%");
-
<select id="USER_LIST" resultType="com.haoyun.POJO.User"> select * from user where name like #{value} </select>
-
或者直接拼接字符串
-
配置解析
主要了解的配置
- configuration配置
- properties 属性
- settings 设置
- typeAliases 类型别名
- typeHandlers 类型处理器
- objectFactory对象工厂
- plugins 插件
- environments 环境配置
- environment 环境变量
- transactionManager 事务管理器
- dataSource数据源
- databaseIdProvider数据库厂商标识
- mappers 映射器
环境配置(environments)
<?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>
<!--可以写多套环境配置,s复数,可以编写多套配置环境,但是default只能选择一套 -->
<environments default="Test">
<!--这样就会选择Test的环境配置-->
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL-true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
<environment id="Test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL-true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--映射-->
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>
可以配置多套环境,但还是只能选择一个环境,在default处切换
事务管理器(transactionManager)
-
Mybatis中的事务管理器分两种类型
- JDBC
- 使用了JDBC的提交和回滚设置,依赖于从数据源得到的连接来管理事务作用域
- Managed
- 这个配置几乎没做什么,不提交不回滚,默认情况下会关闭连接,一些容器并不希望关闭,通过设置来阻止
- JDBC
-
如果正在使用Spring和Mybatis就不用配置这些东西了
-
<transactionManager type="JDBC"/>
数据源(dataSource)
连接数据库,dbcp,c3p0、druid
主要用于连接数据库,三个数据源类型
- unpooled 没数据库连接池
- pooled 数据库连接池 默认的
- JNDI 正常连接
<dataSource type="POOLED">
属性(properties)
-
可以通过properties来实现引用配置文件
-
Demo
- 编写一个配置文件
- 引入
-
编写配置文件,创建在resource文件夹下
-
driver = com.mysql.cj.jdbc.Driver url= jdbc:mysql://localhost:3306/mybatis?useSSL-true&useUnicode=true&characterEncoding=UTF-8 username =root password = 123456
-
在mybatis-config.xml中引入配置文件
-
<mappers> <mapper resource="UserMapper.xml"/> </mappers> <properties></properties> </configuration>
-
假如插入在了末尾,configuration是会报错的
-
提示着插入顺序有问题,应该,先插入properties,然后是settings、、、、、
-
<?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"/> <!--可以写多套环境配置,s复数,可以编写多套配置环境,但是default只能选择一套 --> <environments default="Test"> <!--这样就会选择Test的环境配置--> <environment id="Test"> <transactionManager type="JDBC"/> <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> <!--映射--> <mappers> <mapper resource="UserMapper.xml"/> </mappers> </configuration>
-
value就需要这样引用
-
还可以在properties标签中添加键值对,
-
<properties resource="db.properties"> <property name="username" value="root"/> <property name="password" value="123"/> </properties>
-
这里在xml中添加了键值对,但是配置是错误的,但是还能通过测试,所以写在db.properties中的配置文件的优先级是比较高的
-
已经学了三种方法
- 直接写
- 写在properties中引入
- 在properties标签中添加键值对
类型别名(typeAliases)
就是设置一个短的名字,比如有些名字,要带上很长的包名,能减少冗长的编写
编写的位置要注意
<typeAliases>
<typeAlias type="com.haoyun.POJO.User" alias="User"/>
</typeAliases>
-
也可以指定一个包名,Mybatis会去指定的包名下搜索,JavaBean
-
<typeAliases> <package name="com.haoyun.POJO"/> </typeAliases>
-
写参数类型的时候写小写,mybatis就会通过包名路径去找,写大写也没所谓
还可以使用注解
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Alias("hello")
public class User {
private int id;
private String name;
private String pwd;
}
使用Alias注解将User的别名设置成了hello
就找不到那个JavaBean了
Java的内建类型也是有别名的,都不区分大小写
比如返回值是个int,可以写成 _int
设置(settings)
有很多的设置,不用全部记录,不懂的去翻文档就行,简单记录几个
- 开启缓存,懒加载
- 懒加载之前在jsp说过,就是为了让页面启动的更快,让一些不是很重要的操作之后再加载,先让界面加载出来,程序会看着比较流畅,让对象延迟加载
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5qR2vWIp-1597893958698)(…/…/…/AppData/Roaming/Typora/typora-user-images/image-20200816225503876.png)]
- 自动生成主键
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fi83Ar4M-1597893958699)(https://gitee.com/haoyunlwh/Typoraimage/raw/master/img/20200816225558.png)]
- 驼峰命名转换,再Java中使用的比较多的就是驼峰命名,但是在数据库中,字段名通常使用 下划线 来隔开单词,因为在Oracle数据库中,所有字段名都是大写的格式,只有通过中间加入下划线才能提高可读性,这是下划线命名的由来,可以将数据库列名映射为驼峰命名
其他设置
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins插件
- mybatis-generator-core
- 能自动根据数据库生成mybatis,sql文件,完成crud工作
- mybatis-plus
- 还能简化mybatis操作,crud都不用写,只要写一些复杂的操作
- 通用mapper
- 注册绑定mapper文件
- 四种方法,第二种不要用,第三种需要配置文件和Mapper接口同名,还必须要在同一个包下,第四种,选择存放的包,但是和第三种的情况是一样的,如果配置文件和mapper不同名就会出问题,建议使用第一种方法
- mybatis-generator-core
生命周期和作用域
- 生命周期、和作用域,是至关重要的,因为错误的使用会导致非常严重的 并发问题
- SqlSessionFactoryBuilder:
- 一旦创建了SqlSessionFactory,就不需要他了
- 放在局部变量
- SqlSessionFactory
- 可以想想为数据库连接池
- 应该一直存在,多次创建浪费资源,在高并发环境下,容易崩溃
- 全局作用域
- SqlSession
- 每个线程都应该有自己的SqlSession,可以理解为连接到连接池的一个请求
- sqlSession实例不是线程安全,因此不能被共享
- 最佳的作用域,不能放在一个类的静态域,甚至一个类的实例变量也不行,
- 需要关闭,否则占用资源
ResultMap结果集映射
解决属性名和字段名不一致的问题,之前是实体类和数据库字段一致的,现在测试不一致的情况
这会涉及到一个mybatis自动映射的问题,和手动映射的方法
-
数据库字段和实体类名字不同,产生的错误
-
@Data @AllArgsConstructor @NoArgsConstructor @ToString @Alias("hello") public class User { private int id; private String name; private String password; }
-
字段名pwd不同,运行产生的结果也不同,这里顺便玩了下设置别名
-
@Test public void Test(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User userById = mapper.getUserById(1); System.out.println(userById); sqlSession.close(); }
-
运行出来的结果pwd字段没有查询到
-
<mapper namespace="com.haoyun.dao.UserMapper"> <select id="getUserById" parameterType="int" resultType="hello" > select * from mybatis.user where id = #{id} </select> </mapper>
-
最蠢的解决方法就是设置sql语句的别名
-
<select id="getUserById" parameterType="int" resultType="hello" > select id,name,pwd password from mybatis.user where id = #{id} </select>
-
当然用的不是这种方法
-
现在resultType是具体的类型
实现resultMap
-
结果集映射
-
将数据库的字段,映射成Java实体类的属性
-
<mapper namespace="com.haoyun.dao.UserMapper"> <resultMap id="UserMap" type="hello"> <result column="id" property="id"/> <result column="name" property="name"/> <result column="pwd" property="password"/> </resultMap> <select id="getUserById" parameterType="int" resultMap="UserMap" > select * from mybatis.user where id = #{id} </select> </mapper>
-
这是比较简单的,要编写比较难的就需要在resultMap进行嵌套
-
对于简单的语句并不用写resultMap语句,有些是自己已经映射好了的,比如id和id,name和name字段名和属性名都是一样的,只要写不一样的就好
-
<resultMap id="UserMap" type="hello"> <result column="pwd" property="password"/> </resultMap>
-
已经自动将字段名映射到对应的实体类属性上了
-
如果世界总是这么简单就好了
之后讲一对多和多对一的例子,会更需要resultMap
日志
日志工厂
如果一个数据库操作出现了异常,要进行排错,日志是一个好工具
曾经是:sout,debug
现在是日志工厂
在mybatis-config.xml中的settings有这么一个设置,logImpl
- SL4J 重点
- LOG4J
- LOG4J2
- JDK_LOGGING Java自带的日志输出
- STDOUT_LOGGING 工具包日志输出
- NO_LOGGING 不输出日志
mybatis中具体使用哪一个日志实现,在设置中设定
stdout_logging标准日志输出
添加logImpl设置
设置在mybatis-config.xml中的设置需要按照规则顺序来
忘记了顺序能故意打错,等报错看
要放在properties和typeAliases的中间
添加设置的时候最好直接从官方文档复制,不要靠脑子记,打错任何一点都会报错
比如这一段logImpl打错了,表面是不会报错的,运行的时候就会出问题
到了这里还很难找
下面一句会有The setting LogImpl is not know,看到下面才能知道他给的错误提示
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
所以不要打错,多一个空格都不行
- 从opening开始读,打开jdbc连接
- 创建connection
- 设置自动提交为false
- 预编译sql
- 传递的参数parameter 1个 integer类型
- 查询的列
- 查询的行
- 总共有几条记录
- 最后才是结果
- returned connection 返回到数据库连接池pool
Log4J
可以将日志输出到控制台、文件、GUI组件
- 可以设置每一条的输出格式
- 设置日志级别
- 开于通过配置文件配置
主要操作
-
导入包
-
<!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
-
添加配置文件,面向百度编程,搜索log4j的properties
-
#将等级得debug的日志信息输出到console和file中两个目的地,console和file的定义在下面的代码 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/haoyun.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
-
<settings> <setting name="logImpl" value="LOG4J"/> </settings>
-
主要内容还是类似的
简单使用
-
导入包的时候要注意不要导错包了,要导入apache的log4j
-
public class UserMapperTest { static Logger logger = Logger.getLogger(UserMapperTest.class);
-
public void log4jTest(){ logger.info("info:Test"); logger.debug("debug:Test"); logger.error("error:Test"); }
-
打开看日志文件
-
就是比较清晰明了,工作中要多使用
-
还有其他的日志级别,自己去学习
分页实现
-
两种方法实现分页
- 直接修改sql语句
- 使用RowBounds
-
修改sql语句
-
/*实现分页*/ List<User> getUserByLimit(Map<String,Integer> map);
-
这里使用到Map作为结果集映射,使用map传递参数,比较方便
-
<select id="getUserByLimit" parameterType="map" resultMap="UserMap"> select * from mybatis.user limit #{startIndex},#{pageSize} </select>
-
@Test public void getUserByLimitTest(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); HashMap<String, Integer> limitMap = new HashMap<String, Integer>(); limitMap.put("startIndex",0); limitMap.put("pageSize",2); List<User> userByLimit = mapper.getUserByLimit(limitMap); for (User user : userByLimit) { System.out.println(user); } sqlSession.close(); }
-
-
使用RowBounds实现分页
-
虽然没有直接修改sql来的快,但是比较复合java面向对象的概念
-
通过代码的层面实现分页
-
/*通过RowBounds实现分页*/ List<User> getUserByRowBounds();
-
<select id="getUserByRowBounds" resultMap="UserMap"> select * from mybatis.user </select>
-
@Test public void getUserByRowBoundsTest(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); RowBounds rowBounds = new RowBounds(0,2); List<User> userByLimit = sqlSession.selectList("com.haoyun.dao.UserMapper.getUserByRowBounds", null, rowBounds); for (User user : userByLimit) { System.out.println(user); } sqlSession.close(); }
-
-
还有分页插件比如:PageHelper
- 要使用就去看文档就行
注解开发
Mybatis使用的比较多的是配置文件,之后是使用的比较多的是注解开发
面向接口编程
解耦,可拓展,提高复用,分层开发中,上层不用管具体的实现,大家都遵循共同的标准,使得开发变得容易,规范性更好,
-
面向对象系统中,系统各种功能可能是由许许多多的不同对象协作完成,各个对象内部如何实现对系统涉及人员来说不重要
-
对象之间协作是关键,同类之间的通信,模块之间的通信,是在系统设计之初要考虑的,面向接口编程就是按照这种思想来编程
关于接口的理解:
- 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。
- 接口的本身反应了系统设计人员对系统的抽象理解
- 接口应有两类:
- 对一个 个体的抽象,他可以对应为一个抽象体(abstract class)
- 对一个 个体某一方面的抽象,即形成一个抽象面(interface)
- 一个体有可能有多个抽象面,抽象体与抽象面是有区别的
三个面向区别:
- 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法
- 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现
- 接口设计与非接口设计是针对复用技术而言的,与面向对象、过程不是一个问题,更多的体现就是对系统整体的架构
注解开发
-
在接口上编写注解
-
在mybatis-config.xml设置mapper
-
测试
-
底层原理是注解和反射和动态代理
-
在接口上编写注解
-
public interface UserMapper { @Select("select * from user") List<User>getUsers(); }
- 那个user是idea特有的写法,要连接上了才能这么写
-
将mapper映射到配置文件
-
<!--映射--> <mappers> <mapper class="com.haoyun.dao.UserMapper"/> </mappers>
-
测试
-
@Test public void Test(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> users = mapper.getUsers(); for (User user : users) { System.out.println(user); } sqlSession.close(); }
-
得出结果password没查出来,因为字段名和JavaBean类中的属性名不一致,暂时只能修改Bean类中的属性名,没法用resultMap,所以注解只能完成一些简单的操作,最好还是通过配置文件来操作
-
总结:
- 通过DeBug功能,可以看到里面的本质是通过反射机制来实现的
- 配置的一些值都会被注入到sqlSession中去
- 能反射到UserMapper就能解析到里面的接口,里面的注解,底层是使用的动态代理来完成注解的工作
Mybatis执行流程
有些过程是内部的
注解实现CRUD
增删改都需要commit提交事务,一般通过SqlSession.commit()提交,还可以在创建SqlSession时就设置自动提交
-
public static SqlSession getSqlSession() { return sqlSessionFactory.openSession(true); }
-
之前创建的工具类,openSession有选项能设置自动提交
-
public interface SqlSessionFactory { SqlSession openSession(); SqlSession openSession(boolean var1);
-
对应的接口,往下查看实现的方法
-
public SqlSession openSession(boolean autoCommit) { return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, autoCommit); }
-
boolean 是autoCommit,自动提交,看到这里就行,具体的操作怎么实现还能继续往下探究
-
public interface UserMapper { @Select("select * from user where id =#{id} and name = #{name}") User getUserById(@Param("id") int ida,@Param("name") String namea); }
-
然后再mybatis-config.xml中进行注册
-
<mappers> <mapper class="com.haoyun.dao.UserMapper"/> <!--需要配置的接口多了可以使用通配符--> <mapper class="com.haoyun.dao.*"/> </mappers>
-
使用@Param传递参数,可以不用跟形参名称一致,但是要跟上面注解的名称一致
-
其他的CRUD操作将@Select换成Update、Insert、Delete就行,参数要对上
-
@Test public void Test(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User userById = mapper.getUserById(3,"map"); System.out.println(userById); sqlSession.close(); }
-
测试
-
注解还可以和xml配置文件配合来使用
-
接口层通过注解转递参数
-
int deleteById(@Param("deleteParamId") int id);
-
UserMapper.xml通过 #{} 来接收参数
- #{} 和 KaTeX parse error: Expected 'EOF', got '#' at position 10: {} 的区别,#̲可以防止sql注入,不行
-
<mapper namespace="com.haoyun.dao.UserMapper"> <delete id="deleteById" parameterType="int"> delete from user where id = #{deleteParamId} </delete> </mapper>
-
需要注意的是要在主配置文件中注册
-
<mappers> <mapper resource="UserMapper.xml"/> </mappers>
复杂环境(一对多,多对一)实现
- 一对多
- 多对一
多个学生和一个老师的问题
不同的角度分析问题,
-
对于学生而言,关联,多个学生关联一个老师(多对一)
-
对于老师而言,集合,一个老师,有多个学生(一对多 )
在resultMap设置中还有其余一些设置
- association 一个复杂类型的关联
- collection 一个复杂类型的集合
环境搭建
-
添加student表和teacher表
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HWpac04r-1597893958735)(…/…/…/AppData/Roaming/Typora/typora-user-images/image-20200818150653053.png)]
- 要设置外键
-
-
模块结构
-
实体类、实体类接口,接口映射xml文件,核心配置文件、工具类
-
实体类
-
@Data @AllArgsConstructor @NoArgsConstructor public class Student { private int id; private String name; private Teacher teacher; }
-
实体类映射接口
-
public interface TeacherMapper { @Select("select * from teacher where id = #{tid}") List<Teacher> getTeacher(@Param("tid") int id); }
-
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"> <!--命名空间--> <mapper namespace="com.haoyun.dao.TeacherMapper"> </mapper>
-
核心配置文件
-
<mappers> <mapper resource="StudentMapper.xml"/> <mapper resource="TeacherMapper.xml"/> </mappers>
-
简单测试
-
@Test public void teacher(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class); List<Teacher> teacher = mapper.getTeacher(2); for (Teacher teacher1 : teacher) { System.out.println(teacher1); } sqlSession.close(); }
-
-
多对一的处理
- sql可以直接写出来,
select * from student,teacher where teacher.id=student.tid
有两种形式:
- 按照查询嵌套处理
- 按照结果嵌套处理
如何写入方法思路:
- 查询所有学生信息
- 根据查询出来的学生的tid,寻找对应的老师寻找对应的老师
解决问题,主要流程:
- JavaBean类设置别名好操作
- 编写mapper.xml文件
- 使用resultMap的association设置
- 注册核心文件
- 测试
按照查询嵌套处理
-
设置别名
-
@Data @AllArgsConstructor @NoArgsConstructor @Alias("Student") public class Student { private int id; private String name; private Teacher teacher; }
-
@Data @AllArgsConstructor @NoArgsConstructor @Alias("Teacher") public class Teacher { private int id; private String name; }
-
-
编写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"> <mapper namespace="com.haoyun.dao.StudentMapper"> <resultMap id="StudentTeacher" type="Student"> <!--映射名称 返回值类型--> <!-- <result property="name" column="name"/>--> <!--属性名和字段名同名的可以不用写了--> <!--association 关联--> <!--collection 集合--> <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/> <!--这里的property是一个对象,通过tid不能直接获取,javaType设置这个对象的类型,然后加入一个查询getTeacher--> </resultMap> <select id="getStudent" resultMap="StudentTeacher"> select * from student </select> <select id="getTeacher" resultType="Teacher"> <!--这个子查询的#引用因为只有一个,所以名字随便写也会自动匹配--> select * from teacher where id =#{id} </select> </mapper>
-
有点类似子查询,按照查询嵌套处理
-
-
添加核心配置文件映射
-
<mappers> <mapper resource="StudentMapper.xml"/> <mapper resource="TeacherMapper.xml"/> </mappers>
-
-
测试
-
@Test public void teacher(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); List<Student> student = mapper.getStudent(); for (Student student1 : student) { System.out.println(student1); } sqlSession.close(); }
-
按照结果嵌套处理
-
嵌套处理
-
<resultMap id="StudentTeacher2" type="Student"> <result property="id" column="sid"/> <result property="name" column="sname"/> <association property="teacher" javaType="Teacher"> <!--在Student,javaBean中,教师是用属性名teacher的,类型为Teacher,这里面嵌套一个结果集映射--> <result property="name" column="tname"/> </association> </resultMap> <select id="getStudent2" resultMap="StudentTeacher2"> select s.id sid,s.name sname,t.name tname from teacher t,student s where s.tid = t.id; </select>
-
-
测试
- 结果是一样的
-
总结:
- 多对一查询方式主要也就两种
- 子查询
- 联表查询
- 多对一查询方式主要也就两种
一对多的处理
- 重点就是在配置TeacherMapper.xml上
-
编写JavaBean
-
@Data @AllArgsConstructor @NoArgsConstructor @Alias("Teacher") public class Teacher { private int id; private String name; private List<Student> students; }
-
一个老师对应多个学生,学生是一个集合用list装起来
-
@Data @AllArgsConstructor @NoArgsConstructor @Alias("Student") public class Student { private int id; private String name; private int tid; }
-
学生的一些属性
-
-
编写TeacherMapper.xml文件时要先理清关系,要做的是个一个老师有哪些学生,所以先定位哪个老师
-
这样就能查出老师id为1的老师有哪些学生
-
需要传递一个老师对应的id
-
所以编写接口的时候需要传递id
-
List<Teacher> getTeacherById(@Param("id")int id);
-
<mapper namespace="com.haoyun.dao.TeacherMapper"> <resultMap id="TeacherStudent" type="Teacher"> <result property="id" column="tid"/> <result property="name" column="tname"/> <collection property="students" ofType="Student"> <result property="id" column="sid"/> <result property="name" column="sname"/> <result property="tid" column="tid"/> </collection> </resultMap> <select id="getTeacherById" resultMap="TeacherStudent"> select s.id sid,s.name sname,t.name tname,t.id tid from teacher t,student s where s.tid=t.id and t.id = #{id} </select> </mapper>
-
因为这条select语句采用了很多别名,所以编写map的时候需要多加几个结果集映射
-
这是按照结果嵌套来处理的
-
按照查询嵌套
-
<select id="getTeacherById2" resultMap="TeacherStudent2"> select * from teacher where id = #{id} </select> <resultMap id="TeacherStudent2" type="Teacher"> <collection property="students" javaType="ArrayList" ofType="Student" select="student" column="id"/> </resultMap> <select id="student" resultType="Student"> select * from student where tid = #{id} </select>
-
查询嵌套,先查询一个再查询一个
小结:
- 关联 association(多对一)
- 集合 collection(一对多)
- javaType & ofType
- javaType 用来指定实体类中属性的类型
- ofType 用来指定映射到List或者集合中的POJO类型(List),泛型中的约束类型
- 注意点
- 保证sql的可读性
- 注意属性名和字段问题
- 使用日志好进行错误排查,建议使用LOG4J
慢SQL,比较慢,就是少自己写,多搜集点效率高的sql语句,到时遇到了直接用
面试高频:
- Mysql引擎
- InnoDB底层原理
- 索引
- 索应优化
动态SQL
官方文档已经写的非常详细
动态Sql是指根据不同的条件生成不同的SQL语句
之前是手动拼接的,拼接sql语句是很麻烦的
四个标签:
- if
- choose(when,oherwise)
- trim(where,set)
- foreach
动态SQL环境搭建
- 数据库
- maven导包
- 实体类
- Mapper.xml
- 核心配置文件
- setting驼峰命名转换
- 工具类
- IDutils通过UUID实现随机ID
都是比较常用的套路
开始
-
创建数据库
-
Maven导包
-
<dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.11</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </dependency> </dependencies>
-
-
编写POJO实体类
-
@Data @NoArgsConstructor @Alias("Blog") public class Blog { private String id; private String title; private String author; private Date createTime; //添加驼峰命名映射setting /*这个名字会映射成create_time*/ /*mapUnderscoreToCamelCase*/ private int views; }
-
-
编写接口以及接口映射
-
编写好实体类就写好对应的接口,接口写好写映射,然后把映射添加到核心配置文件中去
-
public interface blogMapper { @Select("select * from blog") List<Blog> getblogs(); int addBook(Blog blog); }
-
暂时写了两个接口,一个使用注解一个使用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.haoyun.dao.blogMapper"> <insert id="addBook" parameterType="Blog"> insert into mybatis.blog (id,title,author,create_time,views) values (#{id},#{title},#{author},#{createTime},#{views}); </insert> </mapper>
-
因为添加了驼峰命名的设置,所以这里传递参数是填写的createTime
-
-
编写核心配置文件
-
使用的是外部配置文件
-
driver = com.mysql.cj.jdbc.Driver url= jdbc:mysql://localhost:3306/mybatis?useSSL=false -TRUE&useUnicode=true&characterEncoding=UTF-8 username =root password = 123456
-
<?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="mapUnderscoreToCamelCase" value="true"/> </settings> <typeAliases> <package name="com.haoyun.POJO"/> </typeAliases> <!--可以写多套环境配置,s复数,可以编写多套配置环境,但是default只能选择一套 --> <environments default="Test"> <!--这样就会选择Test的环境配置--> <environment id="Test"> <transactionManager type="JDBC"/> <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> <!--映射--> <mappers> <mapper class="com.haoyun.dao.blogMapper"/> <mapper resource="blogMapper.xml"/> </mappers> </configuration>
-
要注意setting标签编写的位置
-
设置 mapUnderscoreToCamelCase为true,名字不要打错
-
https://mybatis.org/mybatis-3/zh/configuration.html#settings
-
-
编写工具类
-
MybatisUtil这个工具类使用过好多次了,用来创建sqlsession
-
public class MybatisUtil { private static SqlSessionFactory sqlSessionFactory = null; static { InputStream inputStream = null; try { inputStream = Resources.getResourceAsStream("mybatis-config.xml"); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); } }
-
再写一个随机生成ID的,使用UUID方法
-
public class IDutils { public static String getId(){ return UUID.randomUUID().toString().replaceAll("-",""); } }
-
将 “-” 替换为 “” 空
-
1a19b16580bb46588863d96fa4822d4f测试得到的id
-
-
环境整体结构
-
测试
-
public class blogTest { @Test public void Test() { SqlSession sqlSession = MybatisUtil.getSqlSession(); blogMapper mapper = sqlSession.getMapper(blogMapper.class); List<Blog> getblogs = mapper.getblogs(); for (Blog getblog : getblogs) { System.out.println(getblog); } sqlSession.close(); } @Test public void TestInsert() { SqlSession sqlSession = MybatisUtil.getSqlSession(); blogMapper mapper = sqlSession.getMapper(blogMapper.class); Blog blog = new Blog(); blog.setId(IDutils.getId()); blog.setTitle("TestTitle"); blog.setAuthor("TestAuthor"); //生成时间记得选择util包的Date blog.setCreateTime(new Date()); blog.setViews(1200); mapper.addBook(blog); sqlSession.commit(); sqlSession.close(); } @Test public void IDutilsTest() { System.out.println(IDutils.getId()); } }
动态SQL之IF语句
就是再Mapper.xml中添加if判断,实现sql的拼接,可以判断是否传递了对应列的参数来指定拼接对应的sql
流程:
-
添加接口方法
-
设置mapper.xml
- 加入if判断
-
测试
-
添加接口发方法
-
List<Blog> queryBlogIF(Map map);
-
-
设置mapper.xml
-
<select id="queryBlogIF" parameterType="map" resultType="Blog"> select * from blog <where> <if test="title!= null"> and title = #{title} </if> <if test="author != null"> and author =#{author} </if> </where> </select>
-
参数类型为map,返回值类型为Blog,这个添加了重命名
-
为了保证if条件都不符合,不进行拼接sql语句的情况,不能写成这样,这样拼接上是直接跟着and或or的,所以在if外要包上一层
-
select * from blog where
-
如果传递了参数,进行判断,然后拼接sql
-
-
测试
-
@Test public void queryBlogIFTest(){ SqlSession sqlSession = MybatisUtil.getSqlSession(); blogMapper mapper = sqlSession.getMapper(blogMapper.class); HashMap hashMap = new HashMap(); hashMap.put("title","sadfasdf"); hashMap.put("author","l"); List<Blog> blogs = mapper.queryBlogIF(hashMap); for (Blog blog : blogs) { System.out.println(blog); } sqlSession.close(); }
-
结果
-
可以选择传入一个空的hashMap,查询的就是全表的信息
-
trim(where,set)
trim 切除
这个主要的功能就是,拼接字符串,然后删除语句后缀的逗号
<select id="updateBlog" parameterType="map" >
update blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author},
</if>
</set>
where id = #{id}
</select>
根据编写内容不同选择不同的标签,这里是更新,所以加入了set标签,使用方法也是一样的,只是这里拼接到where的时候会取除末尾的标签中语句的逗号,当然,两个if都没用上,会直接报错的,因为set之后直接拼接where,造成语法错误
@Test
public void queryBlogIFTest(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
blogMapper mapper = sqlSession.getMapper(blogMapper.class);
HashMap hashMap = new HashMap();
hashMap.put("title","sadsdf");
hashMap.put("author","test");
hashMap.put("id","1");
mapper.updateBlog(hashMap);
sqlSession.commit();
sqlSession.close();
}
最后author末尾的逗号是被消除了
choose(when,otherwise )
choose 选择
when 什么情况下
otherwise 否则
规则有点像switch,只去匹配case中的一个值,唯一不同的就是otherwise必须添加参数,如果走了where就不会走otherwise,where都没走就会走otherwise,执行顺序是从上往下执行的,先判定上面的where
choose语句
<select id="queryBlogChoose" parameterType="map" resultType="Blog">
select * from blog
<where>
<choose>
<when test="title != null">
and title = #{title}
</when>
<when test="author!= null">
and author = #{author}
</when>
<otherwise>
and views = #{views}
</otherwise>
</choose>
</where>
</select>
多种情况测试
-
不填参数
-
@Test public void queryBlogIFTest(){ SqlSession sqlSession = MybatisUtil.getSqlSession(); blogMapper mapper = sqlSession.getMapper(blogMapper.class); HashMap hashMap = new HashMap(); //hashMap.put("title","sadfasdf"); //hashMap.put("author","l"); //hashMap.put("views","55"); List<Blog> blogs = mapper.queryBlogChoose(hashMap); for (Blog blog : blogs) { System.out.println(blog); } sqlSession.close(); }
-
自动的添加了views条件但是parameter为null
-
没查询出来
-
hashMap.put("views",10);
-
所以前面的when没有参数就会添加otherwise中的语句
-
加入中间when值
-
hashMap.put("author","l"); hashMap.put("views",10);
-
加入了中间的author when判断和otherwise判断
-
但是只执行了when的语句,otherwise并没有执行
-
when优先级
-
hashMap.put("title","sadfasdf"); hashMap.put("author","l"); hashMap.put("views",10);
-
三条都加入了
-
但是只执行了第一条
-
所以第一条的优先级最高
总结:
- 什么参数都不加的情况下会选择otherwise中的语句
- 第一个多条when条件,从上往下判断,执行排在上面的
- 只选择一条,包括otherwise
共同的父亲trim
如果前置有set关键字,就会删除无关的逗号,prefix 前缀
指定的是一个where,如果存在就会删除指定的内容
这是定制化sql用的一些东西
SQL片段
有一些重复的语句,可以提取出代码片段,实现sql语句的复用
<sql id="if_title_author">
<if test="title!= null">
and title = #{title}
</if>
<if test="author != null">
and author =#{author}
</if>
</sql>
<select id="queryBlogIF" parameterType="map" resultType="Blog">
select * from blog
<where>
<include refid="if_title_author"/>
</where>
</select>
最好sql片段不要编写太复杂的事情,要不然重用效率就变低了
不要存在where标签,where是要根据条件语句来查询的加入就不好实现复用
foreach
<select id="queryBlogForEach" parameterType="map" resultType="Blog">
select * from blog
<where>
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id = ${id}
</foreach>
</where>
</select>
传入一个map类型参数,返回值是一个Blog类型
collection 传递进一个集合,集合的名称为ids,那么用map传递参数的时候就要对应ids名进行传参
里面的每个元素是id,传递了集合就会进行自动的遍历集合,传递参数进id中,
open开始,加入where是为了解决select * from where 1=1 后面必须跟指定条件成立的语句,要不然动态sql没有进入foreach就会变成 select * from where 导致语句错误,加入where标签,还能根据判断去除不必要的and或or
这里就是open开始,填装了一个and (,注意,and和( 之间是有一个空格的,要不然where会识别不出来
在日志中查看得出的结果就是这样
本来用来连接其他条件的语句,不连接其他条件,and没有被去除,导致语句出错
close foreach结束时添加
separator 分隔符,用来分隔集合中元素的分隔符
最后给集合元素传参
@Test
public void queryBlogForEachTest(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
blogMapper mapper = sqlSession.getMapper(blogMapper.class);
HashMap hashMap = new HashMap();
ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add("1");
arrayList.add("2");
arrayList.add("3");
hashMap.put("ids",arrayList);
List<Blog> blogs = mapper.queryBlogForEach(hashMap);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.commit();
sqlSession.close();
}
总结:
动态sql,本质还是sql,只是在sql层面,去执行一个逻辑代码,按照sql格式进行拼接,排列组合,去除不必要的字符
先在Mysql中写出完整的sql,再去修改实现动态sql,实现通用
多去看官方文档
怎么使用普通的sql查询还是不够的,需要多去了解更深层次的知识
- mysql引擎
- InnoDB底层原理
- 索引
- 索引优化
Mybatis缓存
什么是缓存:
连接数据库是很耗资源的,将查询出来的结果存放在一个可以直接取到的地方–>内存:缓存
要查询相同数据时候直接走缓存区执行,就不用再连接数据库了
用户把查询的数据放在内存中,就不用反复去查询
缓存主要解决高并发系统的性能问题
三高问题:
- 高并发
- 靠可用
- 高性能
中间的是服务器,右边的是数据库
读操作是比较容易的,为了让读操作更快,以前使用过一个叫做Memacache的服务,作为缓存实现,将常用的数据存放再Memacache服务器中,读取的时候就快一些,然后数据库还会涉及到读写的问题,多个数据库如何读写,写操作,怎么保证数据库的一致性,读写性能要求不同,如何分配数据库进行读写,哪些数据库作为读数据库,哪些作为写数据库,分工进行
适合使用缓存的数据是那些不经常改变的数据
多数时间都是在进行读操作
Mybatis缓存
- mybatis,通过配置来定义缓存,可以提升查询效率
- 默认定义了两级缓存:一级缓存和二级缓存
- 默认情况,一级缓存开启,(sqlsession级别的缓存,称为本地缓存)
- 二级缓存需要手动开启和配置,基于namespace级别的缓存,命名空间
- 为了提高扩展性,mybatis定义了缓存接口,Cache,可以通过实现Cache接口来自定义二级缓存
一级缓存
- 一级缓存也叫本地缓存:SqlSession
- 与数据库同一次会话期间查询到的数据会放在本地缓存中
- 如果还需要获取相同的参数,直接从缓存中拿,没必要再去查询数据库
主要步骤还是和之前差不多,因为一级缓存是默认的,所以不用进行什么配置
@Select("select * from blog where id = #{id}")
List<Blog> selectBlog(Map map);
void updateBlog(Map map);
添加两个接口进行测试
@Test
public void SqlSessionCacheTest(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
blogMapper mapper = sqlSession.getMapper(blogMapper.class);
HashMap hashMap = new HashMap();
hashMap.put("id","1");
Blog blog2 = mapper.selectBlog(hashMap);
System.out.println(blog2);
hashMap.put("id","1");
Blog blog1 = mapper.selectBlog(hashMap);
System.out.println(blog1);
System.out.println(blog1 == blog2);
sqlSession.close();
}
从connection创建到将connection放置回pool的过程中,只进行了一次查询,而且判断这两个对象,是完全相同的,使用的是同一个索引
查询id 1和id 2时就不一样的了,查询的不是同一个条件,所以查询了两次,这就是cache的区别,对查询相同的数据直接从缓存中拿
遇到增删改操作之后缓存会被刷新,因为数据变更了,肯定要刷新,还会被不定期刷新
测试
-
@Test public void SqlSessionCacheTest(){ SqlSession sqlSession = MybatisUtil.getSqlSession(); blogMapper mapper = sqlSession.getMapper(blogMapper.class); HashMap hashMap = new HashMap(); hashMap.put("id","1"); Blog blog2 = mapper.selectBlog(hashMap); System.out.println(blog2); hashMap.put("title","asdf"); hashMap.put("author","zcv"); hashMap.put("id","2"); mapper.updateBlog(hashMap); sqlSession.commit(); //记得要提交 hashMap.put("id","1"); Blog blog1 = mapper.selectBlog(hashMap); System.out.println(blog1); System.out.println(blog1 == blog2); sqlSession.close(); }
-
缓存失效情况
-
查看不同的东西
-
增删改操作,改变原来的数据,必定刷新缓存
-
查询不同的mapper.xml
-
手动清理缓存
-
sqlSession.clearCache();
-
加这一句就手动清理缓存
-
-
一级缓存是默认开启的,也无法关闭
-
二级缓存
- 二级缓存也叫全局缓存,一级缓存作用域太低,
- 基于namespace级别的缓存,一个命名空间,对应一个二级缓存
- 工作机制:
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
- 如果会话关闭,一级缓存中的数据被保存到二级缓存中
- 新建的会话查询信息,就可以从二级缓存中获取内容
- 不同的mapper查出的数据会放在自己对应的缓存中
步骤:
-
核心配置文件开启全局缓存
-
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> <setting name="logImpl" value="LOG4J"/> <setting name="cacheEnabled" value="true"/> </settings>
-
虽然是默认开启的,但是显示一下
-
-
配置Mapper.xml
-
<mapper namespace="com.haoyun.dao.blogMapper"> <!-- <cache/>--> <cache eviction="FIFO" flushInterval="600000" size="512" readOnly="true"/> <insert id="addBook" parameterType="Blog" flushCache="true">
-
可以详细设置,也可以直接全局设置,对应的标签也能设置是否要加入缓存
-
测试就不测试了,多个sqlsession之间是能共用缓存的
-
遇到错误提示,未序列化
-
提示内容
- Caused by:java.io.NotSerializableException:POJO对象
-
解决方法就是去序列化POJO类
-
public class Blog implements Serializable {
小结:
只要开启了二级缓存在同一个mapper下就有效
所有的数据都会放在一级缓存中,只有当前会话提交,或者关闭的时候,才会提交到二级缓存中
缓存原理
-
先去二级缓存找,二级缓存没有再去一级缓存找,最后找数据库
-
查询优化
-
<insert id="addBook" parameterType="Blog" flushCache="false">
-
比如这个插入太频繁了,根本用不到缓存,所以把它设置未false,不加入缓存
-
自定义缓存-ehcache
Ehcache是一种广泛使用的开源java分布式缓存,主要面向通用缓存
感知不强
流程:
- 导包
- 添加配置文件
- 设置mapper.xml
这个就不整了,知道就行,反正之后也不会用
之后都是使用Redis数据库来做缓存
Redis是非关系型数据库,是K-V键值对形式的