1.Mybatis简介
- 说明:笔记是B站狂神说相关视频的学习笔记
- 持久化:就是将程序的数据在持久状态和瞬时状态转化的过程
- 持久层:完成持久化工作的代码块,统称为Dao层
- MyBatis:简化数据库连接和操作数据库的操作的半持久框架,是目前的主流
2.第一个Mybatis项目
2.1.配置数据库
= 创建数据库,表,表数据
CREATE TABLE `user` ( `id` int(20) NOT NULL AUTO_INCREMENT,#id不为0,自增 `name` varchar(30) DEFAULT NULL,#name默认null `pwd` varchar(30) DEFAULT NULL,#pwd默认null PRIMARY KEY (`id`)#主键索引=id )ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; INSERT INTO `user` VALUES(1,'狂神','123456'); INSERT INTO `user` VALUES(2,'张三','123456'); INSERT INTO `user` VALUES(3,'李四','123456');
2.2.搭建环境
父工程导包
<dependency> <!--mysql驱动--> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.48</version> </dependency> <dependency> <!--mybatis官网--> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <dependency> <!-- junit测试--> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency>
子工程加载资源过滤器
防止后缀为properties和xml配置文件无法加载
<!--资源过滤器,防止导入资源失败问题,最好在父子pom.xml里都加入一下代码--> <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>
配置resources /mybais-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&useUnicode=true&characterEncoding=utf-8"/> <property name="username" value="root"/> <property name="password" value="admin"/> </dataSource> </environment> </environments> <!--mapper注入mybatis--> <mappers> <mapper resource="com/ssl/dao/UserMapper.xml"/> </mappers> </configuration>
编写工厂工具类
- spring整合mybatis后,这个操作再mybats-config.xml中配置
public class MyBatisUtil { /** * 提升sqlSessionFactory作用域,便于全局使用 */ private static SqlSessionFactory sqlSessionFactory; static { try { /* 使用Mybatis第一步,获取sqlSessionFactory对象 */ String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } /** * sqlSessionFactory对象获取SQLSession实例 */ public static SqlSession getSqlSession() { return sqlSessionFactory.openSession(); } }
2.3.Dao层
- Pojo:User
- Dao:接口;实现类变成了XXXMapper.xml文件
public interface UserDao { List<User> getUserList(); }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--namespace命名空间=要实现的dao接口--> <mapper namespace="com.ssl.dao.UserDao"> <select id="getUserList" resultType="com.ssl.pojo.User"> select * from mybatis.user </select> </mapper>
2.4 测试
资源过滤异常,见上面的子工程配置资源过滤器
mapper注册失败异常:在mybatis-config.xml配置
<mappers> <mapper resource="com/ssl/dao/UserMapper.xml"/> </mappers>
public class UserMapperTest { @Test public void getUserList() { //1 获取是sqlSession对象 SqlSession sqlSession = MyBatisUtil.getSqlSession(); //2 获取的是接口的.class,因为多态 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<User> userList = userMapper.getUserList(); for (User user : userList) { System.out.println(user); } //3 建议:最后关闭sqlSession sqlSession.close(); } }
3. CRUD
3.1 namespace=接口全类名相同
<mapper namespace="com.ssl.dao.UserMapper"> <select id="getUserList" resultType="com.ssl.pojo.User"> select * from mybatis.user </select> </mapper>
3.2 select
- id:接口中的方法名
- parameterType:返回结果
- resultType:方法参数
3.3 insert/update/delete
- 增删改需要添加事务,返回值只有int,不用添加resultType
<mapper namespace="com.ssl.dao.UserMapper"> <!--增删改需要提交事务,没有指定的返回值,都是int,所以不用添加resultType--> <!--添加一个用户,对象中的属性可以直接取出来--> <insert id="addUser" parameterType="com.ssl.pojo.User"> INSERT INTO mybatis.user(id,NAME,pwd) VALUES (#{id},#{name},#{pwd}); </insert> <update id="updateUser" parameterType="com.ssl.pojo.User"> update mybatis.user set name = #{name},pwd=#{pwd} where id=#{id}; </update> <delete id="deleteUserById" parameterType="int"> delete from mybatis.user where id=#{id} </delete> </mapper>
3.4 常见错误
- 增删改sql语句写错
# 增加需要values insert into mybatis.user(id,NAME,pwd) VALUES (#{id},#{name},#{pwd}); # 修改需要set update mybatis.user set name = #{name},pwd=#{pwd} where id=#{id}; # 删除需要from delete from mybatis.user where id=#{id}
- 出现bug,是从后往前看查看原因
3.5 万能map
- 如果数据库字段太多,添加修改需要的bean太多,使用map来封装参数,
- 好处一:避免多余代码
- 好处二:跳过特定的Bean属性,可以随意命名key,保证value是字段的属性就行
3.6 模糊查询
<!--模糊查询1 不推荐--> <select id="getLikeUser1" parameterType="string" resultType="com.ssl.pojo.User"> select * from mybatis.user where name like #{value}; </select> <!--模糊查询2 建议写死--> <select id="getLikeUser2" parameterType="string" resultType="com.ssl.pojo.User"> select * from mybatis.user where name like "%"#{value}"%"; </select>
4 配置解析
4.1 mybatis_config.xml
- 在resource中创建mybatis_config.xml
4.2 mybatis中的配置属性
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
4.3 enviroments(环境)
- 事务管理器:transcationManager,默认“JDBC”
- 连接数据库:默认pooled
<environments default="development"> <environment id="development"> <!--事务管理器:默认JDBC--> <transactionManager type="JDBC"/> <!--连接数据源:默认POOLED--> <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>
4.4 properties(属性)
- db.properies
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf-8 username=root password=123456
4.5 settings(设置)
settings配置 | 解释说明 | 默认状态 |
---|---|---|
cacheEnabled(重要) | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | 默认开启 |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 | 默认关闭 |
mapUnderscoreToCamelCase(重要) | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 | 默认关闭 |
logImpl(最重要) | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J、LOG4J、STDOUT_LOGGING等,默认关闭 |
4.6 typeAliases(类型别名)
- 作用:mapper.xml配置resultType时,简化书写
<!--给各种类取别名,简化使用配置--> <typeAliases> <!--方式一:指定类 <typeAlias alias="User" type="com.ssl.pojo.User"/> --> <!--方式二;指定包,包中的小写作为别名 也可以更改小写名,在类上使用@value(“别名”) --> <package name="com.ssl.pojo"/> </typeAliases>
4.7 plugins(插件)
后期需要加深学习的两大插件,使Mybatis配置更加简单
- Mybatis-Plus
- MyBatis Generator Core
4.8 mappers(映射器)
- 在mybatis-config.xml中配置mapper映射器
<!-- 使用resource相对于类路径的资源引用 --> <mappers> <mapper resource="org/mybatis/builder/AuthorMapper.xml"/> <mapper resource="org/mybatis/builder/BlogMapper.xml"/> <mapper resource="org/mybatis/builder/PostMapper.xml"/> </mappers>
- class和package绑定:接口和mapper配置文件必须同名,是否必须在同一个包下有待学习?
<!-- 使用class映射器接口实现类的完全限定类名 --> <mappers> <mapper class="org.mybatis.builder.AuthorMapper"/> <mapper class="org.mybatis.builder.BlogMapper"/> <mapper class="org.mybatis.builder.PostMapper"/> </mappers> <!-- 使用package+name将包内的映射器接口实现全部注册为映射器 --> <mappers> <package name="org.mybatis.builder"/> </mappers>
4.9 MyBatisUtil(参数作用域)
名称 | 解释说明 |
---|---|
SqlSessionFactoryBuilder | 一旦创建就不再需要它了,作用域是局部变量=静态代码块先加载 |
SqlSessionFactory | 运行期间一直存在,作用域是应用作用域,使用单例模式或者静态单例模式。 |
SqlSession | 连接到数据库的请求,线程不安全,用完后马上关闭;作用域是方法或者请求中,用完就关闭,关闭操作十分重要 |
public class MyBatisUtil { //0 提升第三步中sqlSessionFactory作用域 private static SqlSessionFactory sqlSessionFactory; static { try { //1 获取mybatis配置文件 String resource = "mybatis-config.xml"; //2 获取配置文件的输入流 InputStream inputStream = Resources.getResourceAsStream(resource); //3 使用SqlSessionFactoryBuilder().build()创建sqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } //4 通过getSqlSession获取session public static SqlSession getSqlSession() { return sqlSessionFactory.openSession(true); } }
5 属性名和字段名不一致
5.1 更改User中的pwd->password
- 模拟实现这个过程,数据库中是pwd,更改Pojo中的User属性为pwd,导致不一致
//查询结果:不一致的字段查询结果为null User{id=1, name='狂神', password='null'}
5.2 解决办法
- 更改pojo成员属性名 = 数据库字段名:使用起来太low,不推荐
- 使用结果集映射=resultMap:哪个字段不一致,就使不一致的成员属性property映射到数据库的column
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--namespace命名空间=要实现的dao接口=原来的dao实现类--> <mapper namespace="com.ssl.dao.UserMapper"> <!--更改结果集映射,解决属性名和数据库列名不一致--> <resultMap id="UserMap" type="com.ssl.pojo.User"> <result column="pwd" property="password"/> </resultMap> <!--通过id查询一个用户--> <select id="getUserById" parameterType="int" resultMap="UserMap"> select * from mybatis.user where id = #{id}; </select> </mapper>
6 开启日志
6.1 日志工厂
在setting中配置:name = logImpl 大小写和空格不能错
LOG4J:必须掌握,步骤:setting配置,导包,配置pro,测试时加载
setting配置,+导依赖包
<settings> <setting name="logImpl" value="log4j"/> </settings>
<!--log4j日志--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
log4j.properties:需要时去网上找一份
# log4j日志系统:通用配置 # Define the root logger with appender file # log=D:\logs log4j.rootLogger = DEBUG, FILE, console # 输出到当前目录文件下的log包中 log4j.appender.FILE=org.apache.log4j.FileAppender log4j.appender.FILE.File=./logs/log4j.log # Set the immediate flush to true (default) log4j.appender.FILE.ImmediateFlush=true # Set the threshold to debug mode log4j.appender.FILE.Threshold=debug # Set the threshold to debug mode # 设置日志信息追加 log4j.appender.FILE.Append=true # Set the maximum file size before rollover # 30MB log4j.appender.FILE.MaxFileSize=5KB # Set the backup index log4j.appender.FILE.MaxBackupIndex=2 # Define the layout for file appender log4j.appender.FILE.layout=org.apache.log4j.PatternLayout log4j.appender.FILE.layout.conversionPattern=%m%n # 将日志输出到控制台 log4j.appender.console=org.apache.log4j.ConsoleAppender log4j.appender.console.layout=org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss}]-[%t]-[%F:%L]-[%p]-[%c]-%m%n #log4j.appender.console.layout.ConversionPattern=[%d{yyyy-MM-dd}]-[%t]-[%x]-[%-5p]-[%-10c:%m%n] log4j.appender.console.encoding=UTF-8
test中:
static Logger logger = Logger.getLogger(UserMapperTest.class);
public class UserMapperTest { //使用log4j static Logger logger = Logger.getLogger(UserMapperTest.class); @Test public void getUserById() { //1 获取是sqlSession对象 SqlSession sqlSession = MyBatisUtil.getSqlSession(); //2 获取方式一:getMapper UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = userMapper.getUserById(1); System.out.println(user); //3 建议:最后关闭sqlSession sqlSession.close(); } }
STDOUT_LOGGING :掌握,不用导包,mybatis默认配置了,缺点就是只在控制台显示
<settings> <!-- <setting name="logImpl" value="log4j"/>--> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
- SLF4J
- NO_LOGGING
7 分页
7.1 limit
- 第一个参数:startIndex=开始分页的下标 = (第几页 - 1 )* pageSize
- 第二个参数:pageSize=分页中每页的大小
- 只有一个参数:默认是从第一个元数到该index下标实现分页
select * from user limit startIndex,pageSize;# startIndex=(第几页-1)*pageSize select * from user limit index;# 默认从第一个元素到第index个用户
7.2 实现分页
- map和RowbBounds两者都可以实现分页:推荐使用map分页,因为默认key=#{key}
public interface UserMapper { /** * 根据id获取一个用户 * @param id 指定 * @return User */ User getUserById(int id); /** * 通过map分页数据,推荐使用 * @param map 常用 * @return ListUser */ List<User> getLimitUser(Map<String,Object> map); /** * 了解,不推荐使用,通过RowBounds分页数据 * @return ListUser */ List<User> getLimitUserByRowBounds(); }
<?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.ssl.dao.UserMapper"> <!--更改结果集映射,解决属性名和数据库列名不一致--> <resultMap id="UserMap" type="com.ssl.pojo.User"> <result column="pwd" property="password"/> </resultMap> <!--map分页--> <select id="getLimitUser" parameterType="map" resultMap="UserMap"> select * from mybatis.user limit #{startIndex},#{pageSize}; </select> <!--RowBounds分页--> <select id="getLimitUserByRowBounds" resultMap="UserMap"> select * from mybatis.user ; </select> </mapper>
public class UserMapperTest { @Test public void getLimitUser() { UserMapper userMapper = MyBatisUtil.getSqlSession().getMapper(UserMapper.class); Map<String, Object> map = new HashMap<>(); //mapper.xml会自动寻找map中的key=#{key} map.put("startIndex",0); map.put("pageSize",2); List<User> limitUser = userMapper.getLimitUser(map); for (User user : limitUser) { System.out.println(user); } } @Test public void getLimitUserBrRowBounds() { SqlSession sqlSession = MyBatisUtil.getSqlSession(); //老式的查询分页:使用RowBounds RowBounds rowBounds = new RowBounds(0, 2); //语法麻烦 List<User> usersList = sqlSession.selectList("com.ssl.dao.UserMapper.getLimitUserByRowBounds",0,rowBounds); for (User user : usersList) { System.out.println(user); } } }
7.3 分页插件
- 自学Mybatis PageHelper插件,公司需要就去网站自学
8 注解配置sql语句
8.1 mybaits-config.xml中配置映射器
<!--绑定注解开发的接口 class--> <mappers> <mapper class="com.ssl.dao.UserMapper"/> </mappers>
8.2 缺点
- 如果表中列名和成员属性名不一致,查出来就是null
8.3 注解的CRUD
- 学习@param(非常重要)
- 基本类型、String建议都加上,引用类型不用加
- (uid)中的就是sql中的#{uid}
public interface UserMapper { /** * 使用注解开发,有局限性就是column 必须与 dao接口成员属性名一致,否知输出就是null,查不出来 * 所以注解语句开发,便于使用简单场景 */ @Select("select * from user") List<User> getUsers(); /** * @param id=uid * @return User */ @Select("select * from user where id = #{uid}") User getUserById(@Param("uid") int id); }
- 增删改自动提交事务
public static SqlSession getSqlSession() { //不推荐使用,建议手动提交commit return sqlSessionFactory.openSession(true); }
9 Lombok
9.1 IDEA中安装Lombok插件
9.2 maven安装依赖
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </dependency>
9.3 @Data等注解
- @Data:最常用,自动加上Setter Getter equals tostring,
- @AllArgsConstructor:有参构造
- @NoArgsConstructor:无参构造
9.4 缺点
- 虽然可以混合使用,但多重的构造器的构造器不能重载
- 公司用就用,不用就少用,因为改变了java源码的书写习惯,不利于推广
10 多对一
- 导入Lombok插件和依赖,减少pojo代码
- 新建实体类Student、Teacher和数据库表
- Student中有一个字段tid使用外键约束,关联Teacher
CREATE TABLE `student` ( `id` int(10) NOT NULL, `name` varchar(20) 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 CREATE TABLE `teacher` ( `id` int(10) NOT NULL, `name` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
- 配置环境:pojo和mapper接口
@Data @AllArgsConstructor @NoArgsConstructor public class Student { private Integer id; private String name; //需要关联一个老师类 private Teacher teacher; } @Data @AllArgsConstructor @NoArgsConstructor public class Teacher { private int id; private String name; }
public interface StudentMapper { //子查询 List<Student> getStudents(); //联表查询 List<Student> getStudents1(); }
10.1:子查询=查询嵌套
- mapper.xml
- 以下对于理解非常重要
- javaType=“teacher”配置了别名,大小写都可以,去pojo路径中中找Teacher,然后使用其成员属性tid
<!--方式一:按照查询嵌套处理 = 子查询--> <select id="getStudents" resultMap="resultStudents"> select * from mybatis.student; </select> <resultMap id="resultStudents" type="student"> <!--association:属性是对象时使用--> <!--collection:属性是集合时候用--> <association property="teacher" column="tid" javaType="teacher" select="getTeachers"/> </resultMap> <select id="getTeachers" resultType="teacher"> select * from mybatis.teacher where id = #{tid} </select>
- 测试
@Test public void getStudents() { StudentMapper studentMapper = MyBatisUtil.getSqlSession().getMapper(StudentMapper.class); List<Student> students = studentMapper.getStudents(); for (Student student : students) { System.out.println(student); } /* Student(id=1, name=小红, teacher=Teacher(id=1, name=秦老师)) */ }
10.2:联表查询=结果嵌套
使用多表查询,避免写多个sql
mapper.xml
<!--方式二:按照结果嵌套处理 = 联表查询--> <select id="getStudents1" resultMap="resultStudents1"> select s.id sid,s.name sname,t.name tname from student s,teacher t where s.tid = t.id; </select> <resultMap id="resultStudents1" type="student"> <result property="id" column="sid"/> <result property="name" column="sname"/> <association property="teacher" javaType="teacher"> <result property="name" column="tname"/> </association> </resultMap>
- 测试:与嵌套查询结果没变
@Test public void getStudents1() { StudentMapper studentMapper = MyBatisUtil.getSqlSession().getMapper(StudentMapper.class); List<Student> students = studentMapper.getStudents1(); for (Student student : students) { System.out.println(student); } /* Student(id=1, name=小红, teacher=Teacher(id=0, name=秦老师)) */ }
11 一对多
- 配置环境:pojo和dao下的mapper接口
@Data @AllArgsConstructor @NoArgsConstructor public class Student { private Integer id; private String name; private int tid; } @Data @AllArgsConstructor @NoArgsConstructor public class Teacher { private int id; private String name; //一对多的集合 private List<Student> students; }
public interface StudentMapper { } public interface TeacherMapper { /** * 子查询:按查询嵌套查询 * @param tid * @return */ Teacher getTeacher(@Param("id") int tid); /** * 联表查询:按结果嵌套查询 * @param tid * @return */ Teacher getTeacher1(@Param("id") int tid); }
11.1: 子查询
<!--子查询:按查询嵌套查询--> <select id="getTeacher" resultMap="teacherToStudent"> select id, name from mybatis.teacher where id = #{id} </select> <resultMap id="teacherToStudent" type="teacher"> <result property="id" column="id"/> <result property="name" column="name"/> <!-- column="id"是teacher表中的id--> <collection property="students" javaType="List" ofType="student" column="id" select="getStudentsByTeacherId" /> </resultMap> <select id="getStudentsByTeacherId" resultType="student"> select * from mybatis.student where tid = #{id} </select>
测试:
@Test public void getTeacher() { TeacherMapper teacherMapper = MyBatisUtil.getSqlSession().getMapper(TeacherMapper.class); Teacher teacher = teacherMapper.getTeacher(1); System.out.println(teacher); /* 如果id=0怎么解决?就是collection中配置result property="id" column="id" Teacher(id=1, name=秦老师, students=[Student(id=1, name=小红, tid=1),Student(id=2, name=小明, tid=1)... */ }
11.2: 联表查询
<!--联表查询:按结果嵌套查询--> <select id="getTeacher1" resultMap="teacherAndStudent"> select s.id sid,s.name sname,t.name tname,t.id tid from mybatis.teacher t,mybatis.student s where s.tid=t.id and t.id =#{id} </select> <resultMap id="teacherAndStudent" type="teacher"> <result property="id" column="tid"/> <result property="name" column="tname"/> <!--集合对象用collection绑定,javaType是返回单个属性,不能返回集合, 返回属性是集合用ofType绑定--> <collection property="students" ofType="student"> <result property="id" column="sid"/> <result property="name" column="sname"/> <result property="tid" column="tid"/> </collection> </resultMap>
测试:
@Test public void getTeacher1() { TeacherMapper teacherMapper = MyBatisUtil.getSqlSession().getMapper(TeacherMapper.class); Teacher teacher = teacherMapper.getTeacher1(1); System.out.println(teacher); /* Teacher(id=1, name=秦老师, students=[Student(id=1, name=小红, tid=1), Student(id=2, name=小明, tid=1)... */ }
11.3: 多表查询小结
- 多对一中的一:association
- 一对多中的多:collection
- javaType & ofType
- javaType:指定实体类中的属性的java返回值类型
- ofType:映射List或某些指定的pojo泛型的类型,联想List中的泛型类型Student用ofType绑定
- 注意点:
- 保证SQL的可读性,建议使用联表查询
11.4: 面试题自学补充
- MySQL引擎
- InnoDB底层原理
- 索引和索引优化
12 动态SQL
- 概念:动态 SQL 是 MyBatis 的强大特性之一,简化了原生复杂SQL书写
- 四个判断条件:
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
- 搭建数据库
CREATE TABLE blog( id VARCHAR(50) NOT NULL COMMENT '博客id', title VARCHAR(100) NOT NULL COMMENT '博客标题', author VARBINARY(30) NOT NULL COMMENT'博客作者', # 数据库时间DateTime类型=pojo中的Date类型 # 下划线命名调到pojo中的驼峰式命令,需要mybatis开启驼峰式命令 create_time DATETIME NOT NULL COMMENT'创建时间', views INT(30) NOT NULL COMMENT'浏览量' )ENGINE=INNODB DEFAULT CHARSET = utf8;
- pojo和驼峰式命名
@Data @AllArgsConstructor @NoArgsConstructor public class Blog { private String id; private String title; private String author; /** * 下划线命名调到pojo中的驼峰式命令,需要mybatis开启驼峰式命令 */ private Date createTime; private int views; }
<!--解决驼峰命令,使用setting配置,只能用户数据库中的xx_xx编程bean中的驼峰式--> <settings> <setting name="logImpl" value="STDOUT_LOGGING"/> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>
- Utils
public class MyBatisUtil { /** * 提升sqlSessionFactory作用域,便于全局使用 */ private static SqlSessionFactory sqlSessionFactory; static { try { /* 使用Mybatis第一步,获取sqlSessionFactory对象 */ String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } /** * sqlSessionFactory对象获取SQLSession实例 */ public static SqlSession getSqlSession() { return sqlSessionFactory.openSession(true); } } //随机产生数据库表中的views public class UUIDUtils { public static String getId() { return UUID.randomUUID().toString().replaceAll("-", ""); } @Test public void getUUId() { System.out.println(UUIDUtils.getId()); } }
- mapper接口
public interface BlogMapper { int addBlog(Blog blog); List<Blog> queryBlogByIf(Map map); List<Blog> queryBlogByWhere(Map map); List<Blog> queryBlogByForeach(Map map); }
if
概念:sql常见的场景就是判断使用
方式一:通过< if >直接使用,或者< include>跳转sql使用时候,需要保证where成立,所以需要在sql语句中加上类似
where 1= 1
或者where state = 'active'
等语句- 这里使用了SQL片段复用,见后面讲解
<!--if:通过include跳转到使用,缺点是必须写上判断条件成立 where 1=1--> <select id="queryBlogByIf" parameterType="map" resultType="Blog"> select * from mybatis.blog where 1=1 <include refid="if_title_author_like" /> </select> <sql id="if_title_author_like"> <if test="title !=null"> and title like #{title} </if> <if test="author !=null"> and author like #{author} </if> </sql>
- 方式二:通过< where >和< if >混合使用,就不用手动加上
where 1= 1
<!--if:通过where直接使用/直接使用if判断,但是不推荐。原理:如果test存在,就自动加上where --> <select id="queryBlogByWhere" parameterType="map" resultType="Blog"> select * from mybatis.blog <where> <if test="id !=null"> id like #{id} </if> <if test="views !=null"> and views like #{views} </if> </where> </select>
- 测试:模糊查询需要封装通配符
@Test public void queryBlogByIf() { SqlSession sqlSession = MyBatisUtil.getSqlSession(); BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class); Map<String, String> map = new HashMap<>(); //模糊查询,使用map的好处 map.put("title", "%my%"); List<Blog> blogs = blogMapper.queryBlogByIf(map); System.out.println(blogs); sqlSession.close(); } @Test public void queryBlogByWhere() { SqlSession sqlSession = MyBatisUtil.getSqlSession(); BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class); Map<String, Object> map = new HashMap<>(); //map.put("id","%4%"); map.put("views", "%2%"); List<Blog> blogs = blogMapper.queryBlogByWhere(map); System.out.println(blogs); sqlSession.close(); }
choose
概念:有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
需求:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG
细节:choose只能满足其中一个when\otherwisw;使用
WHERE state = ‘ACTIVE’
等保证where成立
<!--官网案例--> <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>
where
- 单独使用< if > 的缺点:如果没有查询条件或者第一个条件没有满足,就会出现错误的sql语句:
<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 # 第一个条件没有成立 SELECT * FROM BLOG WHERE AND title like ‘someTitle’
- 使用< where >
- 优势:where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
<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>
- 使用自定义 trim 元素来定制 where 元素的功能
- 与< where>等价的< trim>
- prefix="WHERE"满足条件,自动添加的字段:prefixOverrides自动忽略的字段,细节:AND |OR 两者后面都包含了一个空格,这是正确sql的书写要点
- 与< where>等价的< trim>
<trim prefix="WHERE" prefixOverrides="AND |OR "> ... </trim>
set
- 概念:用于动态update语句的叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列
- 等价的trim语句,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)= 也就是说其实mybatis自动在update中set就给你加上了逗号,但是你自己手写加上了,< set> 也会给你忽略掉
<trim prefix="SET" suffixOverrides=","> ... </trim>
<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>
foreach
概念:动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)
原生:
SELECT * FROM blog WHERE 1=1 AND (id=1 OR id=2);
细节:if < where> 多个条件成立时,就会忽略掉原生中and书写
<select id="queryBlogByForeach" parameterType="map" resultType="Blog"> select * from mybatis.blog <where> <foreach collection="ids" item="id" open="(" separator="or" close=")"> id=#{id} </foreach> </where> </select> <!--官网案例:使用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>
SQL片段
- 回顾:前面的< where >结合< if>,我们将公共的SQL语句抽取出来,复用.
- 使用:< sql id= > 标签和< include refid= >引用
- 细节:
- 最好基于单表使用sql片段,多表别的表不一定支持
- 使用sql片段复用,不要使用< where >标签,因为它内置了会忽略掉某些字段
<sql id="if_title_author_like"> <if test="title !=null"> and title like #{title} </if> <if test="author !=null"> and author like #{author} </if> </sql> <select id="queryBlogByIf" parameterType="map" resultType="Blog"> select * from mybatis.blog where 1=1 <include refid="if_title_author_like" /> </select>
bind(了解)
- bind 元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文。比如:
<select id="selectBlogsLike" resultType="Blog"> <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" /> SELECT * FROM BLOG WHERE title LIKE #{pattern} </select>
13 缓存
- 我们再次查询相同数据时候,直接走缓存,就不用再存了,解决速度问题
- 什么样的数据需要用到缓存?
- 经常查询并且不经常改变的数据,可以使用缓存
缓存原理图(重要)
二级缓存工作原理:
- 一次sqlsession是一级缓存,查询操作结束后,是默认保存在一级缓存中的
- 如果开启二级缓存,必须先关闭一级缓存,这时候的缓存数据会保存到二级缓存中
- 第二次查询时候,用户操作会嫌去二级缓存中查找
一级缓存
- 默认情况下,只启用了本地的会话(一级)缓存,它仅仅对一个会话中的数据进行缓存。
- 把一级缓存想象成一个会话中的map,便于理解
- 缓存失效
- 增删改会把所有的sql缓存失效,下次会重写从数据库中查
- 查询不同的东西,查询不同的mapper.xml
- 手动清除缓存:
sqlSession.clearCache();
二级缓存
- mybatis-config,xml开启全局缓存
<settings> <setting name="logImpl" value="STDOUT_LOGGING"/> <!--开启全局缓存 默认是开启的,显示写便于可读--> <setting name="cacheEnabled" value="true"/> </settings>
mappper.xml开启二级缓存:
<!--开启二级缓存--> <cache/>
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
- 读写缓存需要pojo开启序列化操作
@Data @AllArgsConstructor @NoArgsConstructor public class User implements Serializable { private int id; private String name; private String pwd; public static void main(String[] args) { new ArrayList<>(); new HashMap<>(); new LinkedList<>(); } }
开启二级缓存时,可以指定参数
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
- eviction:清楚算法,默认LRU
LRU
– 最近最少使用:移除最长时间不被使用的对象。FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
- flushInterval:刷新缓存间隔,默认无
- size:最多缓存数量,默认1024
- readOnly:只读缓存;写操作会不走缓存,直接从数据库查询,默认是读/写缓存
- eviction:清楚算法,默认LRU
使用二级缓存
- 测试语句
public class MyTest { @Test public void queryUserById() { SqlSession sqlSession = MyBatisUtil.getSqlSession(); SqlSession sqlSession1 = MyBatisUtil.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.queryUserById(1); //关闭上次sqlSession,如果开启二级缓存,就会把这次的一级缓存保存到二级缓存中 sqlSession.close(); System.out.println("================="); UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class); //如果开启二级缓存,下次相同mapper的查询操作会先重二级缓存中查找 User user1 = mapper1.queryUserById(1); sqlSession1.close(); } }
自定义缓存(了解)
- 概念:ehcache是一个分布式缓存,主要面向通用缓存
- 手写或者导入第三方的ehcache缓存依赖
- 所以可以自定义ehcache.xml配置文件
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache --> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.2.0</version> </dependency>
- 在mapper.xml中配置
<cache type="org.mybatis.caches.encache.EhcacheCache"/>
- 在resource中创建ehcache.xml文件:
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false"> <defaultCache eternal="false" maxElementsInMemory="10000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="259200" memoryStoreEvictionPolicy="LRU"/> <cache name="cloud_user" eternal="false" maxElementsInMemory="5000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="1800" memoryStoreEvictionPolicy="LRU"/> <!-- diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下: user.home – 用户主目录 user.dir – 用户当前工作目录 java.io.tmpdir – 默认临时文件路径 --> <diskStore path="java.io.tmpdir/Tmp_EhCache"/> <!-- defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。 --> <!-- name:缓存名称。 maxElementsInMemory:缓存最大数目 maxElementsOnDisk:硬盘最大缓存个数。 eternal:对象是否永久有效,一但设置了,timeout将不起作用。 overflowToDisk:是否保存到磁盘,当系统当机时 timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。 timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。 diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false. diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。 diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。 clearOnFlush:内存数量最大时是否清除。 memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。 FIFO,first in first out,这个是大家最熟的,先进先出。 LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。 LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。 --> </ehcache>
Redis
- 自学,另一个开始