. Mybatis笔记
是一个优秀的持久层框架,支持定制化sql、存储过程和高级映射。
避免了所有jdbc代码和手动设置参数以及获取结果集。
可以使用简单的xml或注解来配置和映射原生类型、接口和Java中的POJO为数据库中的记录。
如何获得mybatis?
数据持久化:将程序的数据在持久状态和瞬时状态转换的过程。
内存:断电即失 ,有些对象不能让他丢掉,所以需要持久化。而且内存太贵了。
数据库持久化,io文件持久化。两种方式都可以完成持久化,后者开销大,所以数据库诞生了。
Dao层、Service层、Controller层…
思路: 搭建环境–>导入Mybatis–>编写代码–>测试
在左面,父工程项目右键,new moudle 还是新建一个普通的maven项目
这样做的好处在于可以直接继承父工程的环境
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "htto://mybatis.org/dtd/mybatis-3-config.dtd"> <!--configuration核心--> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/smbms?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="jiawensili1029"/> </dataSource> </environment> <environment id="tests"> <transactionManager type="JDBC"></transactionManager> <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> </configuration>
// sqlSessionFactoryBuilder ---> sqlSessionFactory // sqlSessionFactory ---> sqlSession // sqlSession包含了面向数据库执行sql命令所需的所有方法 public class MybatisUtils { private static SqlSessionFactory sqlSessionFactory; static { try { // 获取sqlSessionFactory。通过建造者模式来获取 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession getSqlSession(){ // 获取sqlSession。通过工厂模式来获取 // SqlSession sqlSession = sqlSessionFactory.openSession(); // return sqlSession; return sqlSessionFactory.openSession(); } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ewsMSyb5-1625065882163)(/Users/shenhangran/Desktop/学习笔记/我的/Mybatis笔记.assets/image-20210607145330136.png)]
使用标签来写查询语句,返回集里resultType来定义返回的类型,比如resultType=“com.shen.pojo.User” ,用来返回一个结果
resultMap用来返回一堆结果。
先定义pojo类User
Dao接口
public interface UserDao { List<User> getUserList(); }
接口实现类由原来的UseDaoImpl转变为一个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" > <!--namespace=绑定一个对应的Dap/Mapper接口--> <mapper namespace="com.shen.dao.UserDao"> <!--select查询语句--> <select id="getUserList" resultType="com.shen.pojo.User"> select * from smbms_user </select> </mapper>
@Test public void test(){ // 第一步,获得SqlSession对象 SqlSession sqlSession = MybatisUtils.getSqlSession(); // 第二步 获得接口,执行sql UserDao mapper = sqlSession.getMapper(UserDao.class); mapper.getUserList(); }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4FLiAoys-1625065882165)(/Users/shenhangran/Desktop/学习笔记/我的/Mybatis笔记.assets/image-20210607224907328.png)]
绑定异常:在mapper注册中心中,接口是未知的
解决方法:
第一步、
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jfRIXk9p-1625065882166)(/Users/shenhangran/Desktop/学习笔记/我的/Mybatis笔记.assets/image-20210607225242309.png)]
只是这样还不够,除非你把UserMapper.xml放到target目录下,否则还是找不到,不生效
但是,我们又不可能每次都手动的把这个东西放进去啊!
出现这个问题的原因,是因为maven的约定大于配置,可能遇到我们写的配置文件,无法被导出或者生效的问题,解决方案如下
第二步、在pom.xml中添加下面代码
<!--在build中配置resources,来防止我们资源导出失败的问题--> <build> <resources> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </build>
注意,sqlSession不是线程安全的,因此用完必须关闭(放在finally里)
将上面测试代码改为:
@Test public void test(){ // 第一步,获得SqlSession对象 SqlSession sqlSession; try { sqlSession = MybatisUtils.getSqlSession(); // 第二步 获得接口,执行sql UserDao mapper = sqlSession.getMapper(UserDao.class); List<User> userList = mapper.getUserList(); for (User user : userList) { System.out.println(user); } }catch (Exception e){ e.printStackTrace(); }finally { sqlSession.close(); } }
namespace中的包名要和接口的包名一致!
选择,查询语句
编写接口
编写对应的mapper中的sql语句
测试 注意增删改查需要提交事务
假设我们的实体类,有很多字段,但是我们只修改很少的几个字段,则Dao层接口和xml里方法参数类型将不是User,而是Map。具体的#{}里放入map的key就可以自动识别。
多个参数除了使用map意外,还可以使用注解
不要在java层面写"%"+str +"%"
而是要在sql层面写: “%”#{str}"%" 避免sql注入问题!
例子:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QAINsRNy-1625065882167)(/Users/shenhangran/Desktop/学习笔记/我的/Mybatis笔记.assets/image-20210613234000226.png)]
注意返回类型是User,但事实上接口的返回类型是List
configuration(配置) properties(属性) settings(设置) typeAliases(类型别名) typeHandLers(类型处理器) objectFactory(对象工厂) plugins(插件) environments(环境配置) environment(环境变量) transactionManager(事务管理器) dataSource(数据源) databaseIdProvider(数据库厂商标识) mappers(映射器)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-juFKDaAi-1625065882169)(/Users/shenhangran/Desktop/学习笔记/我的/Mybatis笔记.assets/image-20210614231851368.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UTHrjyUl-1625065882170)(/Users/shenhangran/Desktop/学习笔记/我的/Mybatis笔记.assets/image-20210614231942829.png)]
MyBatis默认的事务管理器是JDBC,连接池:POOLED
属性都是可以外部配置且可以动态替换的,既可以在典型的Java属性文件中配置,也可以通过properties元素的子元素来传递。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VD2hzhY3-1625065882170)(/Users/shenhangran/Desktop/学习笔记/我的/Mybatis笔记.assets/image-20210614232635346.png)]
注意,xml里,约定了标签的先后顺序,properties必须放在很前面的位置。
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/smbms?useSSL=TRUE&useUnicode=TRUE&characterEncoding=UTF-8 jdbc.username=root jdbc.password=jiawensili1029
注意,在properties里,driver和url同上面略有区别,true必须大写,不能用& amp;来代表&,driver多个cj,因为不带的已经被淘汰。前面的jdbc.都可以去掉
小结
在xml里
<select id="getUserByUserCode" parameterType="String" resultType="com.shen.pojo.User"> select * from smbms_user where userCode = #{userCode} </select>
com.shen.pojo.User太长了,是冗余的。因此使用别名。
<!--可以给实体类型起别名--> <typeAliases> <typeAlias type="com.shen.pojo.User" alias="user"></typeAlias> </typeAliases>
方法2.指定一个包名,MyBatis会在包名下面搜索需要的JavaBean
扫描实体类的包,它的默认别名就为这个类的类名的首字母小写(使用的时候写成大写也可以)。
<typeAliases> <!--<typeAlias type="com.shen.pojo.User" alias="user"></typeAlias>--> <package name="com.shen.pojo"/> </typeAliases>
第一种方法可以自定义别名,第二种则需要注解自定义别名。实体类较少建议使用第一种,实体类较多建议使用第二种。第二种方式中,可以用注解来起别名。
@Alias("hello") public class User { // 实体类 ... }
<select id="getUserLike" parameterType="String" resultType="hello"> select * from smbms_user where userName like "%"#{username}"%" </select>
Java类型中有一些内建的别名。
比如==_int代表int;int代表Integer==
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c95Lsfos-1625065882171)(/Users/shenhangran/Desktop/学习笔记/我的/Mybatis笔记.assets/image-20210619213305302.png)]
MapperRegistry:注册绑定我们的Mapper文件
<mappers> <!--1.使用相对路径资源引用--> <mapper resource="com/shen/dao/UserMapper.xml"></mapper> <!--2.使用url完全限定资源定位符,不建议使用--> <!--3.使用映射器接口的实现类的完全限定类名--> <mapper class="com.shen.dao.UserMapper"></mapper> <!--4.将包内的映射器接口实现全部注册为映射器--> <package name="com.shen.dao"/> </mappers>
使用方式3和方式4时会遇到的坑:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4bhQCBKp-1625065882172)(/Users/shenhangran/Desktop/学习笔记/我的/Mybatis笔记.assets/image-20210619214923455.png)]
这两个东西很重要,因此错误的使用会导致非常严重的并发问题
SqlSessionFactoryBuilder
SqlSessionFactory
SqlSession
public class User { // 实体类 private Integer id; private String userCodeCodeHa; // 改这里 }
经过测试发现,如果但改这里和所有的userCode参数变为userCodeCodeHa,还是可以从数据库里查到。但是如果把set方法和get方法也改了。比如把setUserCode改为setUserCodeCodeHa以后,就在数据库里找不到这个字段名对应的数据了(输出null)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZM3JKOhe-1625065882172)(/Users/shenhangran/Desktop/学习笔记/我的/Mybatis笔记.assets/image-20210619223025121.png)]
因此可以推测,MyBatis是通过set和get方法的名字,来一一对应java中pojo的实体类的属性名与数据库中字段名的。
方法一:在sql语句里起别名
<select id="getUserByUserCode" parameterType="String" resultType="hello"> select userpassword,usercode as usercodecodeha from smbms_user where userCode = #{userCode} </select>
其实从这个方法,可以看出,mybatis其实是执行sql以后,把结果的那个列名,拼接上“set”,构成了SetUserCodeCodeHa(不区分大小写),然后在java对应的pojo实体类里,找到对应的这个方法,然后把列名对应的属性作为参数,传入这个set方法里。
方法二:resultMap 结果集映射
首先,给select标签里的resultMap随便起个名字,这里叫UserMap吧
<select id="getUserByUserCode" parameterType="String" resultMap="UserMap"> select * from user_test where usercode = #{userCode} </select>
然后,在resultMap标签,做一个映射。注意这里严格区分大小写
<!--column数据库中的字段,property实体类中的属性 严格区分大小写--> <resultMap id="UserMap" type="hello"> <result column="usercode" property="userCodeCodeHa"></result> <!--对于没有别名的这两句,其实可以不写--> <result column="userpassword" property="userPassword"></result> <result column="username" property="userName"></result> </resultMap>
如果一个数据库操作出现异常,可以通过日志工厂来排错。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PEmrUfI0-1625065882173)(/Users/shenhangran/Desktop/学习笔记/我的/Mybatis笔记.assets/image-20210620172235045.png)]
主要掌握LOG4J和STDOUT_LOGGING
在Mybatis中具体使用哪个日志实现,在设置中实现。
<settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cq2cb4Ll-1625065882173)(/Users/shenhangran/Desktop/学习笔记/我的/Mybatis笔记.assets/image-20210620172712062.png)]
STDOUT_LOGGING是标准的日志工厂,可以直接用
其他的,比如LOG4J就需要导包以后才能用,否则找不到类
log4j的apache的一个开源项目。可以控制日志信息输送的目的地是控制台、文件还是GUI组件。可以控制每一条日志的输出格式、定义每一条日志信息的级别。可以通过一个配置文件来灵活地进行配置,不需要修改应用的代码。
先导入log4j的包
<dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies>
log4j.properties
#将等级为DEBUG的日志信息输出到console和file两个目的地。console和file的定义在下面的代码 log4j.rootLogger=debug,file,console #输出到文件相关设置 log4j.appender.file.Append = true log4j.appender.file.MaxFileSize = 10MB log4j.appender.file = org.apache.log4j.RollingFileAppender log4j.appender.file.File = ./log/com.shen 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.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.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
配置log4j为日志的实现
使用
info、error、debug
// 分页 List<User> getUserByLimit(Map<String,Integer> map);
<select id="getUserByLimit" parameterType="map" resultType="user"> select * from smbms_user limit #{startIndex},#{pageSize} </select>
<select id="getUserByRowBounds" resultMap="UserMap"> select * from smbms_user </select>
@Test public void getUserByRowBounds(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); // 其实RowBounds本质也是在调用limit RowBounds rowBounds = new RowBounds(2,5); // 通过Java层面代码实现分页 List<User> list = sqlSession.selectList("com.shen.dao.UserMapper.getUserByRowBounds",null,rowBounds); for(User user : list){ System.out.println(user); } }
7.3 在maven里用一些其他的分页插件,如pageHelper
面向接口编程的根本原因:解耦、提高复用。
不需要mapper.xml,对于简单的sql语句,可以直接在接口中使用注解。
public interface UserMapper { @Select("select * from smbms_user") List<User> getUsers(); }
本质:反射机制实现
底层:动态代理—代理模式!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0lwUtLMC-1625065882174)(/Users/shenhangran/Desktop/学习笔记/我的/Mybatis笔记.assets/image-20210621180320371.png)]
使用maven仓库引入lombok的jar包。
只需要一个注解 Data,就生成了:
无参构造、set、get、toString、hashcode、equals
@NoArgsConstructor @AllArgsConstructor
无参构造和有参构造的注解
例:smbms_user的userRole不是Integer类型,而直接使用Role类型的实体。
Pojo:Role
@Data public class Role { private Integer roleCode; private String roleName; }
Pojo:User
@Data public class User { // 实体类 private Integer id; private String userCode; // # 解决属性名和字段名不一致问题 private String userName; private Role userRole; }
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" > <!--namespace=绑定一个对应的Dap/Mapper接口--> <mapper namespace="com.shen.dao.UserMapper"> <!--这里的userAndRole本质上还是一个User类型的,但因为涉及到复杂属性,不得不另起一个map来描述清楚--> <resultMap id="userAndRole" type="user"> <result property="userCode" column="userCode"></result> <result property="userName" column="userName"></result> <!--复杂的属性需要单独处理,对象:association 集合:collection--> <association property="userRole" column="userRole" javaType="Role" select="getRoleByRoleCode"></association> </resultMap> <select id="getUsersAndRoleName" resultMap="userAndRole"> select * from smbms_user </select> <select id="getRoleByRoleCode" resultType="role"> select * from smbms_role where roleCode = #{roleCode} </select> </mapper>
注意,其中
<select id="getRoleByRoleCode" resultType="role"> select * from smbms_role where roleCode = #{roleCode} </select>
这个查询,不是放在RoleMapper.xml而是放在UserMapper.xml
因为getUsersAndRoleName这个接口的实现,要使用getRoleByRoleCode(这个在RoleMapper接口里定义,在UserMapper.xml里实现)
<association property="userRole" column="userRole" javaType="Role" select="getRoleByRoleCode"></association>
本质其实就是property的userRole对应User类里的Role userRole的实例对象。
column的userRole对应数据库表里smbms_user的userRole(BigInteger类型)
javaType相当于是要使用column作为参数,去select后面那个语句以后的返回类型,正好就是Role类型嘛!因为getRoleByRoleCode返回的是role类型。
以上称之为按照查询嵌套处理
例:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R8BT1I8u-1625065882174)(/Users/shenhangran/Desktop/学习笔记/我的/Mybatis笔记.assets/image-20210621230423305.png)]
返回学生姓名和学生对应的老师的姓名
其实就是对应上面的按照查询嵌套和按照结果嵌套
比如:一个老师拥有多个学生!对于老师而言,就是一对多。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1FSh8Tgh-1625065882175)(/Users/shenhangran/Desktop/学习笔记/我的/Mybatis笔记.assets/image-20210621230804334.png)]
学生类则是单独的和数据库一一对应的pojo
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8qzTr234-1625065882175)(/Users/shenhangran/Desktop/学习笔记/我的/Mybatis笔记.assets/image-20210621232357846.png)]
一对多使用集合Collection 集合中的元素类型不用javaType,而是用ofType表示
动态sql就是指根据不同的条件生成不同的sql语句
<select ....> <if test=" title!=null "> AND title like #{title} </if> </select>
类似于java中的swich语句
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hwpI7ufU-1625065882176)(/Users/shenhangran/Desktop/学习笔记/我的/Mybatis笔记.assets/image-20210623230045371.png)]
两个when条件都满足,但只走了第一个.相当于switch case breake了
where元素只会在至少有一个子元素的条件返回SQL子句的情况下才会插入where,若语句开头为AND或者OR,where元素会自动将它们去除.
where和set都是trim的子元素.使用trim可以自己定制类似where和set的标签,并且定义前缀后缀/前缀覆盖后会覆盖.
前缀/后缀:比如where,如果没有任何子元素,就自动把where去掉
前缀覆盖:比如and和or,如果是第一个子元素,就会自动去掉and和or,第二个开始的子元素会保留and和or
后缀覆盖:比如set的逗号,如果是最后一个set的子元素,会把逗号去掉,不然sql里多一个逗号
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LtH3nzze-1625065882176)(/Users/shenhangran/Desktop/学习笔记/我的/Mybatis笔记.assets/image-20210623225935781.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xbOreIug-1625065882176)(/Users/shenhangran/Desktop/学习笔记/我的/Mybatis笔记.assets/image-20210623231256430.png)]
item就是从collection里遍历的每一项
例:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dCbEWoqk-1625065882177)(/Users/shenhangran/Desktop/学习笔记/我的/Mybatis笔记.assets/image-20210623231413483.png)]
因为where标签很"智能",所以不需要写1=1
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6k44Wt1h-1625065882177)(/Users/shenhangran/Desktop/学习笔记/我的/Mybatis笔记.assets/image-20210623231810060.png)]
ids使用map传入
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GInpd2Kl-1625065882178)(/Users/shenhangran/Desktop/学习笔记/我的/Mybatis笔记.assets/image-20210623230700242.png)]
可以用来存取一个sql片段,避免重写一遍.
注意事项:
sqlSession级别的缓存。一级缓存默认开启,且无法关闭,只在一次sqlSession中有效
比如在一次sqlSession会话中,查询两次同一条语句,但sql其实只走了一次。
一级缓存就是一个Map
实现方式:LRU(最长时间不用的就被顶替)、FIFO(最先使用过的被顶替)
缓存失效的情况:
查询不同的东西
增删改操作可能改变原来的数据,所以必定会刷新缓存
查询不同的Mapper.xml
手动清理缓存
sqlSession.clearCache();
namespace级别的缓存
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Btx4QK2r-1625065882179)(/Users/shenhangran/Desktop/学习笔记/我的/Mybatis笔记.assets/image-20210623234039925.png)]
步骤:
<settings> <!--开启二级缓存--> <setting name="cacheEnabled" value="true"/> </settings>
<!--在当前mapper中使用二级缓存 每隔60秒刷新--> <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
刷新时间单位为毫秒。
记得在具体的一个sql语句的地方开启cache
<select id="getUsersAndRoleName" resultMap="userAndRole" useCache="true"> select * from smbms_user </select>
小结
readOnly设置为false时,下面的代码会输出false,否则会输出true。原因是因为事先了Serializable接口,深拷贝,哈希变了,但因为开了二级缓存,所以还是只走了一遍sql!
public void test1(){ SqlSession sqlSession1 = MybatisUtils.getSqlSession(); UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class); List<User> users1 = mapper1.getUsersAndRoleName(); sqlSession1.close(); // 得先关闭。因为只有一级缓存崩了以后二级缓存才生效 System.out.println("------"); SqlSession sqlSession2 = MybatisUtils.getSqlSession(); UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class); List<User> users2 = mapper2.getUsersAndRoleName(); System.out.println(users1==users2); sqlSession2.close(); }
总结:
对于用户的命令而言,先从二级缓存里看有没有,没有的话看一级缓存里有没有,还没有的话才去数据库里查找!
maven导入ehcache可以自定义缓存,也可以用现成的。
type属性指定的类必须实现org.mybatis.cache.Cache接口且提供一个String id