Demo
导入相关依赖
1 2 3 4 5 6 7 8 9 10 <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.5.0</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.19</version > </dependency >
编写MyBatis核心配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?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" /> <environments default ="development" > <environment id ="development" > <transactionManager type ="JDBC" /> <dataSource type ="POOLED" > <property name ="driver" value ="${driver}" /> <property name ="url" value ="${url}" /> <property name ="username" value ="${username}" /> <property name ="password" value ="${password}" /> </dataSource > </environment > </environments > <mappers > <package name ="com.atomsk.dao" /> </mappers > </configuration >
编写MyBatis工具类,便于使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class MybatisUtils { private static SqlSessionFactory factory; static { try { String resource = "mybatis-config.xml" ; InputStream inputStream = Resources.getResourceAsStream(resource); factory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { System.out.println("创建SqlSession工厂失败" ); e.printStackTrace(); } } public static SqlSession getSqlSession () { return factory.openSession(); } }
创建实体类
编写Mapper接口类
1 2 3 4 @Mapper public interface UserMapper { List<User> getAllUser () ; }
编写Mapper.xml配置文件
1 2 3 4 5 6 7 8 <?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.atomsk.dao.UserMapper" > <select id ="getAllUser" resultType ="com.atomsk.pojo.User" > select * from user; </select > </mapper >
Mavan静态资源过滤
1 2 3 4 5 6 7 8 9 10 11 12 <build > <resources > <resource > <directory > src/main/java</directory > <includes > <include > **/*.properties</include > <include > **/*.xml</include > </includes > <filtering > false</filtering > </resource > </resources > </build >
编写测试类
1 2 3 4 5 6 7 8 9 10 11 12 public class UserMapperTest { @Test public void test () { SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class ) ; List<User> users = userMapper.getAllUser(); for (User user : users) { System.out.println(user); } sqlSession.close(); } }
CRUD
select
参数:
rersultType:SQL语句返回值类型。【完整的类名或者别名】
parameterType:传入SQL语句的参数类型 。【万能的Map,可以多尝试使用】
示例:根据 用户名 和 密码 查询用户
思路一:直接在方法中传递参数
1、在接口方法的参数前加 @Param属性
2、Sql语句编写的时候,直接取@Param中设置的值即可,不需要单独设置参数类型
1 2 3 4 5 User selectUserByNP(@Param("username") String username,@Param("password") String pwd); <select id ="selectUserByNP" resultType ="com.kuang.pojo.User" > select * from user where name = #{username} and pwd = #{password} </select >
思路二:使用万能的Map
1、在接口方法中,参数直接传递Map;
1 User selectUserByNP2 (Map<String,Object> map) ;
2、编写sql语句的时候,需要传递参数类型,参数类型为map
1 2 3 <select id ="selectUserByNP2" parameterType ="map" resultType ="com.kuang.pojo.User" > select * from user where name = #{username} and pwd = #{password} </select >
3、在使用方法的时候,Map的 key 为 sql中取的值即可,没有顺序要求!
1 2 3 4 Map<String, Object> map = new HashMap<String, Object>(); map.put("username" ,"小明" ); map.put("password" ,"123456" ); User user = mapper.selectUserByNP2(map);
总结:如果参数过多,我们可以考虑直接使用Map实现,如果参数比较少,直接传递参数即可
insert update delete 操作需要提交事务!
关于@Param
@Param注解用于给方法参数起一个名字。以下是总结的使用原则:
在方法只接受一个参数的情况下,可以不使用@Param。
在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。
如果参数是 JavaBean , 则不能使用@Param。
不使用@Param注解时,参数只能有一个,并且是Javabean。
#与$的区别
配置解析
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。
能配置的内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 configuration(配置) properties(属性) settings(设置) typeAliases(类型别名) typeHandlers(类型处理器) objectFactory(对象工厂) plugins(插件) environments(环境配置) environment(环境变量) transactionManager(事务管理器) dataSource(数据源) databaseIdProvider(数据库厂商标识) mappers(映射器)
注意元素节点的顺序!顺序不对会报错
可以阅读 mybatis-config.xml 上面的dtd的头文件!
environments元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <configuration > <properties resource ="db.properties" /> <environments default ="development" > <environment id ="development" > <transactionManager type ="JDBC" /> <dataSource type ="POOLED" > <property name ="driver" value ="${driver}" /> <property name ="url" value ="${url}" /> <property name ="username" value ="${username}" /> <property name ="password" value ="${password}" /> </dataSource > </environment > </environments > <mappers > <mapper resource ="mapper/UserMapper.xml" /> </mappers > </configuration >
mappers元素
mappers
引入资源方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <mappers > <mapper resource ="org/mybatis/builder/PostMapper.xml" /> </mappers > <mappers > <mapper url ="file:///var/mappers/AuthorMapper.xml" /> </mappers > <mappers > <mapper class ="org.mybatis.builder.AuthorMapper" /> </mappers > <mappers > <package name ="org.mybatis.builder" /> </mappers >
Mapper文件
1 2 3 4 5 6 7 <?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.kuang.mapper.UserMapper" > </mapper >
namespace中文意思:命名空间,作用如下:
namespace的命名必须跟某个接口同名
接口中的方法与映射文件中sql语句id应该一一对应
typeAliases优化
类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。
1 2 3 4 <typeAliases > <typeAlias type ="com.kuang.pojo.User" alias ="User" /> </typeAliases >
当这样配置时,User
可以用在任何使用com.kuang.pojo.User
的地方。
也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:
1 2 3 <typeAliases > <package name ="com.kuang.pojo" /> </typeAliases >
每一个在包 com.kuang.pojo
中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。
若有注解,则别名为其注解值。见下面的例子:
1 2 3 4 @Alias ("user" )public class User { ... }
日志 如果一个 数据库相关的操作出现了问题,我们可以根据输出的SQL语句快速排查问题。
对于以往的开发过程,我们会经常使用到debug模式来调节,跟踪我们的代码执行过程。但是现在使用Mybatis是基于接口,配置文件的源代码执行过程。因此,我们必须选择日志工具来作为我们开发,调节程序的工具。
Mybatis内置的日志工厂提供日志功能,具体的日志实现有以下几种工具:
SLF4J
Apache Commons Logging
Log4j 2
Log4j
JDK logging
具体选择哪个日志实现工具由MyBatis的内置日志工厂确定。它会使用最先找到的(按上文列举的顺序查找)。如果一个都未找到,日志功能就会被禁用。
标准日志实现 指定 MyBatis 应该使用哪个日志记录实现。如果此设置不存在,则会自动发现日志记录实现。
1 2 3 <settings > <setting name ="logImpl" value ="STDOUT_LOGGING" /> </settings >
测试,可以看到控制台有大量的输出!我们可以通过这些输出来判断程序到底哪里出了Bug
Log4j 简介:
通过使用Log4j,我们可以控制日志信息输送的目的地:控制台,文本,GUI组件….
我们也可以控制每一条日志的输出格式;
通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
使用步骤:
1、导入log4j的包
1 2 3 4 5 <dependency > <groupId > log4j</groupId > <artifactId > log4j</artifactId > <version > 1.2.17</version > </dependency >
2、配置文件编写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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/kuang.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
3、setting设置日志实现
1 2 3 <settings > <setting name ="logImpl" value ="LOG4J" /> </settings >
4、在程序中使用Log4j进行输出!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static Logger logger = Logger.getLogger(MyTest.class ) ;@Test public void selectUser () { logger.info("info:进入selectUser方法" ); logger.debug("debug:进入selectUser方法" ); logger.error("error: 进入selectUser方法" ); SqlSession session = MybatisUtils.getSession(); UserMapper mapper = session.getMapper(UserMapper.class ) ; List<User> users = mapper.selectUser(); for (User user: users){ System.out.println(user); } session.close(); }
5、测试,看控制台输出!
使用Log4j 输出日志
可以看到还生成了一个日志的文件 【需要修改file的日志级别】
分页
limit实现分页
思考:为什么需要分页?
在学习mybatis等持久层框架的时候,会经常对数据进行增删改查操作,使用最多的是对数据库进行查询操作,如果查询大量数据的时候,我们往往使用分页进行查询,也就是每次处理小部分数据,这样对数据库压力就在可控范围内。
使用Limit实现分页
1 2 3 4 5 6 7 8 9 10 11 #语法 SELECT * FROM table LIMIT stratIndex,pageSize SELECT * FROM table LIMIT 5,10; // 检索记录行 6-15 #为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1: SELECT * FROM table LIMIT 95,-1; // 检索记录行 96-last. #如果只给定一个参数,它表示返回最大的记录行数目: SELECT * FROM table LIMIT 5; //检索前 5 个记录行 #换句话说,LIMIT n 等价于 LIMIT 0,n。
步骤:
1、Mapper接口,参数为map
1 2 List<User> selectUser (Map<String,Integer> map) ;
2、修改Mapper文件
1 2 3 <select id ="selectUser" parameterType ="map" resultType ="user" > select * from user limit #{startIndex},#{pageSize} </select >
3、在测试类中传入参数测试
推断:起始位置 = (当前页面 - 1 ) * 页面大小
int currentPage = 1; //第几页 int pageSize = 2; //每页显示几个 Map<String,Integer> map = new HashMap<String,Integer>(); map.put(“startIndex”,(currentPage-1)*pageSize); map.put(“pageSize”,pageSize);
List users = mapper.selectUser(map); ```
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 PageHelper 官方文档:https://pagehelper.github.io/ ## 多对一的处理 多对一的理解: - 多个学生对应一个老师 - 如果对于学生这边,就是一个多对一的现象,即从学生这边关联一个老师! > 数据库设计 ```mysql 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`), KEY `fktid` (`tid`), CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`) ) ENGINE=INNODB DEFAULT CHARSET=utf8
按结果嵌套处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <select id ="getStudents2" 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 ="Student" > <id property ="id" column ="sid" /> <result property ="name" column ="sname" /> <association property ="teacher" javaType ="Teacher" > <result property ="name" column ="tname" /> </association > </resultMap >
按查询嵌套处理
1 2 3 4 5 6 7 8 9 10 <select id ="getStudents" resultMap ="StudentTeacher" > select * from student </select > <resultMap id ="StudentTeacher" type ="Student" > <association property ="teacher" column ="tid" javaType ="Teacher" select ="getTeacher" /> </resultMap > <select id ="getTeacher" resultType ="teacher" > select * from teacher where id = #{id} </select >
一对多的处理 一对多的理解:
一个老师拥有多个学生
如果对于老师这边,就是一个一对多的现象,即从一个老师下面拥有一群学生(集合)!
按结果嵌套处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <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 t.id=#{id} </select > <resultMap id ="TeacherStudent" type ="Teacher" > <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 >
按查询嵌套处理
1 2 3 4 5 6 7 8 9 10 11 12 <select id ="getTeacher2" resultMap ="TeacherStudent2" > select * from teacher where id = #{id} </select > <resultMap id ="TeacherStudent2" type ="Teacher" > <collection property ="students" javaType ="ArrayList" ofType ="Student" column ="id" select ="getStudentByTeacherId" /> </resultMap > <select id ="getStudentByTeacherId" resultType ="Student" > select * from student where tid = #{id} </select >
小结
按照查询进行嵌套处理就像SQL中的子查询
按照结果进行嵌套处理就像SQL中的联表查询
association是用于一对一和多对一,而collection是用于一对多和多对多的关系
JavaType 和ofType 都是用来指定对象类型的
JavaType是用来指定pojo中属性的类型
ofType指定的是映射到list集合属性中pojo的类型。
延迟加载 在对应的四种表关系中
一对多,多对多:通常情况下我们都是采用延迟加载。
多对一,一对一:通常情况下我们都是采用立即加载。
开启延迟加载
1 2 3 <settings > <setting name ="lazyLoadingEnabled" value ="true" /> <setting name ="aggressiveLazyLoading" value ="false" /> </settings >
动态SQL
介绍
什么是动态SQL:动态SQL指的是根据不同的查询条件 , 生成不同的Sql语句.
if 和Where语句
修改上面的SQL语句;
1 2 3 4 5 6 7 8 9 10 11 <select id ="queryBlogIf" parameterType ="map" resultType ="blog" > select * from blog <where > <if test ="title != null" > title = #{title} </if > <if test ="author != null" > and author = #{author} </if > </where > </select >
这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。
Set
同理,上面的对于查询 SQL 语句包含 where 关键字,如果在进行更新操作的时候,含有 set 关键词,我们怎么处理呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 <update 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}; </update >
choose语句
有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <select id ="queryBlogChoose" parameterType ="map" resultType ="blog" > select * from blog <where > <choose > <when test ="title != null" > title = #{title} </when > <when test ="author != null" > and author = #{author} </when > <otherwise > and views = #{views} </otherwise > </choose > </where > </select >
SQL片段
有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。
提取SQL片段:
1 2 3 4 5 6 7 8 <sql id ="if-title-author" > <if test ="title != null" > title = #{title} </if > <if test ="author != null" > and author = #{author} </if > </sql >
引用SQL片段:
1 2 3 4 5 6 7 8 <select id ="queryBlogIf" parameterType ="map" resultType ="blog" > select * from blog <where > <include refid ="if-title-author" > </include > </where > </select >
注意:
①、最好基于 单表来定义 sql 片段,提高片段的可重用性
②、在 sql 片段中不要包括 where
Foreach
将数据库中前三个数据的id修改为1,2,3;
需求:我们需要查询 blog 表中 id 分别为1,2,3的博客信息
2、编写SQL语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <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 >
小结 :其实动态 sql 语句的编写往往就是一个拼接的问题,为了保证拼接准确,我们最好首先要写原生的 sql 语句出来,然后在通过 mybatis 动态sql 对照着改,防止出错。多在实践中使用才是熟练掌握它的技巧。
缓存
Mybatis缓存
MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
MyBatis系统中默认定义了两级缓存:一级缓存 和二级缓存
默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
一级缓存 一级缓存也叫本地缓存:
与数据库同一次会话期间查询到的数据会放在本地缓存中。
以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;
一级缓存失效的四种情况
一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它;
一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求!
当前sqlSession的缓存中不存在这个数据
sqlSession相同,调用了增删改,commit(),close()等方法时,就会清空一级缓存
sqlSession相同,手动清除了一级缓存,sqlSession.clearCache()
二级缓存
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
由同一个SqlSessionFactory对象创建的SqlSession共享其缓存
基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
使用二级缓存时,所缓存的类一定要序列化
使用步骤
1、让mybatis框架支持二级缓存【mybatis-config.xml】
1 <setting name ="cacheEnabled" value ="true" />
2、让当前mapper映射文件支持二级缓存【xxxMapper.xml】
1 2 3 4 5 6 7 8 9 10 1. <mapper namespace ="" > <cache /> </mapper > 或者 2. <cache eviction ="FIFO" flushInterval ="60000" size ="512" readOnly ="true" /> 这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
3、让当前的操作支持二级缓存(在select标签中配置 userCache="true"
)
1 2 3 <select id ="queryUserById" resultType ="user" useCache ="true" > select * from user where id = #{uid} </select >
4、代码测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Test public void testQueryUserById () { SqlSession session = MybatisUtils.getSession(); SqlSession session2 = MybatisUtils.getSession(); UserMapper mapper = session.getMapper(UserMapper.class ) ; UserMapper mapper2 = session2.getMapper(UserMapper.class ) ; User user = mapper.queryUserById(1 ); System.out.println(user); session.close(); User user2 = mapper2.queryUserById(1 ); System.out.println(user2); System.out.println(user==user2); session2.close(); }
结论
只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据
查出的数据都会被默认先放在一级缓存中
只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中
缓存原理图
EhCache
第三方缓存实现–EhCache: