mybatis学习
MyBatis
什么是MyBatis
myBatis是一款优秀的持久层框架,它支持自定义sql、存储过程以及高级映射。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录
myBatis是一个轻量级的ORM框架。相对重量级的ORM框架Hibernate而言,mybatis是一个半自动框架,而Hibernate是一个全自动框架。而为什么相对与功能强大的Hibernate全自动化不使用而使用相对较多的是mybatis
原因:
- Hibernate
- 配置繁琐
- Hibernate对功能进行全方面的封装,将用户操作,转化为sql语句,然后进行数据库操作,整个转换过程是Hibernate自动完成的,开发无法控制,如果要进行sql语句的优化是没法实现的。所以在数据库压力逐渐增大时,Hibernate框架性能问题就出现了。
- 而 mybatis将sql语句的定义控制权完全交给了开发者,并且暴露一套API,对JDBC中:事务,参数,查询结果等进行了配置处理。
持久化
数据持久化
- 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
- 内存:断电即失
- 数据持久化的方式:数据库jdbc,io文件
为什么需要持久化?有些对象,不能让他丢失。内存太贵,并且有限
持久层:完成持久化工作的代码,Dao层
ORM
ORM:Object relation mapping 对象关系映射
将数据库中的信息和java实体类进行映射
功能架构
我们把mybatis功能架构分为三层:
- API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库,接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理
- 数据处理层:负责具体的sql查找、sql解析、sql执行和执行结果映射处理。它主要的目的是根据调用的请求完成一次数据库操作
- 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
第一个Mybatis程序
mybatis官网:https://mybatis.org/mybatis-3/
1、创建数据库表
CREATE TABLE `stu` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(11) DEFAULT NULL, `age` int(11) DEFAULT NULL, `weight` int(11) DEFAULT NULL, `address` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
2、使用idea搭建一个maven项目
在pom.xml中导入依赖
//mysql依赖 <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.22</version> </dependency> //mybatis依赖 <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency>
3、编写mybatis的核心配置文件
在resources资源目录中创建mybatis.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> <!-- properties 我们要在运行环境中使用配置mysql的配置信息,需要引入资源文件 resource : 引入资源文件 相对路径 url : 绝对路径 --> <properties resource="jdbc.properties"/> <!-- 全局设置 全局设置mybatis 存在一些默认配置 一般配置 缓存 和 日志 cacheEnabled : 是否开启映射文件中的查询缓存 logImpl: 日志实现类配置 默认自动查找 --> <settings> <setting name="logImpl" value="LOG4J"/> </settings> <typeAliases> <!-- type : 具体类型 alias: 别名 --> <!--为某个具体的类定义别名 <typeAlias type="com.zll.domain.Student" alias="zhangsan" />--> <!-- 为某个包下面的类使用别名 别名默认是类名 或者 遵循 小驼峰类型名 Student 或者 student --> <package name="com.zll.pojo"/> </typeAliases> <!-- 配置分页插件 --> <plugins> <!-- 配置插件 --> <plugin interceptor="com.github.pagehelper.PageInterceptor" /> </plugins> <!-- 数据源环境 --> <!-- default : 采用的具体环境 environment : 具体环境 id: 具体环境的唯一标识 transactionManager : 事务管理器 type: 事务类型 JDBC : 这个配置直接使用了 JDBC 的提交和回滚设施(使用) MANAGED : 它从不提交或回滚一个连接 dataSource: 数据源 type : 数据源类型 POOLED : 使用连接池 UNPOOLED : 不使用连接池 每次请求打开关闭连接 JNDI : 本地连接,早期是使用服务器的连接配置 现在基本不用 property : 数据源的属性配置 --> <environments default="dev"> <!-- 具体环境 --> <environment id="dev"> <!-- 事务管理器 --> <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> <!-- 引入数据操作的 ORM配置文件 --> <!-- 映射器文件 --> <!-- 根据包名 引入映射文件 在maven处理程序时 会默认将java目录下的非*.java排除 如果一定要将xml与接口放在同样的包下 需要单独配置maven 不推荐 --> <!-- <package name="com.zll.dao"/>--> <!-- resource : 使用相对路径引入映射配置文件 (推荐) class : 单个的接口进行注册 xml配置文件和接口必须在同一个包下 且同名 url: 使用绝对路径 引入配置文件 --> <mappers> <mapper resource="UserMapper.xml"/> </mappers> </configuration>
configuration中的标签(必须顺序编写不然会报错)
- properties:属性配置文件,一般用于数据库链接配置 因为xml中有些符号需要转义
- settings:全局设置,如日志配置
- typeAliases:类型别名 ,有package和typeAlias
- typeHandlers:类型处理器mybatis内置存在很多进行类型适配的处理器一般不配置,mybatis会进行自动适配
- objectFactory:对象工厂
- objectWrapperFactory:对象包装类工厂
- reflectorFactory:反射工厂
- plugins:插件mybatis支持插件式扩展 拔插式扩展 一般使用分页插件
- envirnoments:运行环境
- databaseIdProvider:
- mappers:引入映射文件
4、编写dao接口和mapper配置文件
以前操作jdbcDao层时,会编写一个Dao接口在编写接口的实现类在实现类中进行对数据的jdbc操作编写sql语句,而接口的实现类现在可以不写了可以通过使用Mapper配置文件完成,而如何将dao接口与Mapper配置文件进行绑定,以前是进行实现这个接口,现在使用mapper标签中的namespace属性设置全类名来进行绑定,可以看做绑定后那么这个mapper配置文件就是Dao接口的实现类了,而这个mapper中的sql标签的如:select等,也可以看做是Dao接口中的方法的实现重写方法,而方法名要与标签通过标签的id属性进行绑定,命名要相同。
<?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 的值与数据操作接口全名称一致 --> <mapper namespace="com.zll.pojo.StudentDao"> <!-- mapper : 映射文件根标签 namespace : mybatis默认将namespace的值与指令的ID 拼接产生的字符串 作为 map 容器中的key namespace必须和数据操作接口全路径一致,形成绑定关系 cache-ref : 关联的缓存 cache : 开启二级缓存 resultMap : 结果映射关系 parameterMap : 废弃 sql : sql 片段 部分sql 以下为指令标签: insert : 新增指令 update : 修改指令 delete : 删除指令 select : 查询指令 --> <!-- 当查询结果列别名和类属性名不一致,可以使用结果映射 resultMap : 结果映射标签 id : 结果映射关系唯一标识 type : 映射的类 result: 查询结果的具体映射关系 column : 结果的列别名 jdbcType: 数据库中该列类型 可缺省(mybatis 自动识别) property: 对应的type类型中的属性名 javaType: 类属性类型 可缺省(mybatis 自动识别) typeHandler: 自定义类型处理器 可缺省(mybatis 自动识别) id : 本质上也是结果 等同于result 但是一般使用id标签标识查询结果中的主键 --> <resultMap id="Base_Ailas" type="com.zll.pojo.Student"> <id column="id" property="id"/> <!--数据库中的列名是name 而实体类中的字段名是studnetName就可以进行一下配置映射 --> <result column="name" property="studentName"/> </resultMap> <!-- sql 片段 : 部分sql 为了提高代码的复用性 mybatis 提供了sql 片段标签 ,可以将公共sql 抽离. 使用sql标签包裹 id 值为其唯一标识,其它需要使用的地方可以进行引用 在其他标签中可以使用 include 标签引用 include refid="id值" --> <sql id="common" > id,name,age,weight,address </sql> <!-- select 查询指令标签 id: 当前namespace 唯一标识 (重点) 与接口中定义的方法名称一致 resultType : 查询结果类型( 重点 ) resultMap : 自定义的查询结果映射关系 (重点) resultType 和 resultMap 二者必须存在一个,且只能存在一个 以下做了解 parameterType: 参数类型 可以缺省 fetchSize : 结果长度控制 flushCache : 清理缓存 useCache : 是否使用缓存 parameterMap : 废弃 --> <select id="listStudent" resultMap="Base_Ailas"> select <include refid="common"/> from stu; </select> <!-- 根据ID 查询 --> <select id="selectStudentById" resultType="com.powernode.domain.Student"> select <include refid="common" /> from student where id=${id} </select> <!-- insert : 插入指令 id: 唯一标识 parameterType: 参数类型 可以缺省 useGeneratedKeys: 使用已生成主键 keyColumn : 主键的列名 keyProperty : 主键列对应属性名 --> <insert id="insert" parameterType="com.powernode.domain.Student" useGeneratedKeys="true" keyColumn="id" keyProperty="id"> insert into student value (0,#{name},#{age},#{sex},#{address},#{birth}) </insert> <update id="update" parameterType="com.powernode.domain.Student"> update student set name = #{name}, age = #{age}, sex = #{sex},address = #{address},birth = #{birth} where id = #{id} </update> <!-- where name like concat('%',#{name},'%') where name like '%${name}%' --> <select id="selectLike" resultMap="Base_Ailas"> select <include refid="common" /> from student where name like concat('%',#{name},'%') </select> </mapper>
5、编写工具类获得SqlSession类
/** * SqlSession 工具类 */ public class SqlSessionUtil { final static SqlSessionFactory sqlSessionFactory; static { String config = "mybatis.xml"; //找到配置文件 InputStream inputStream = null; try { inputStream = Resources.getResourceAsStream(config); } catch (IOException e) { e.printStackTrace(); } // 加载 解析 // 通过工厂构建模式 加载解析 配置文件 // 创建Sql Session 工厂 : SQL 会话工厂 产生SQL 会话对象 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } public static SqlSession getSqlSession(){ // 创建一个具体的SQL 会话对象 SqlSession sqlSession = sqlSessionFactory.openSession(); return sqlSession; } }
6、编写实体类
//使用了lombok插件 @Data @AllArgsConstructor @NoArgsConstructor public class Student { private Integer id; private String studentName; private Integer age; private Integer weight; private String address; }
7.编写测试类
public static void main(String[] args) { /* * 1. 加载所有的配置文件 * 2. 解析配置文件 * 3. xml文件 解析成一个对象 Configuration 包含了所有的配置信息 数据源信息 sql指令 * 4. 使用Map 容器,将每个SQL指令 存储在Map 容器中, namespace+指令ID值作为key,将每个指令标签封装成 MappedStatement 对象 * SqlCommandType : 指令类型 * SqlSource : 解析后的sql语句 * * 5. 创建了SqlSession 会话对象 * sqlSessionFactory.openSession(); : Configuration * 事务管理对象 * 6. sqlSession.selectOne : * 1. 先查询所有 * 2. 所有中取1个 如果不是一个则报错 * 7. 从全局的map容器中获取MappedStatement对象 * 7.1. 从MappedStatement获取指令类型 * 7.2. 从MappedStatement获取SQL指令 sql语句 * 8. 创建PrepareStatement SQL 预处理对象 * 9. 执行SQL * 10. 将查询结果集封装成指令中预定义的结果类型 * */ SqlSession sqlSession = FactorySession.getSqlSession(); //两种方式 执行sql //方式一:指令ID的方式 List<Student> students = sqlSession.selectList("com.zll.pojo.StudentDao.listStudent"); //==================================================================================== //方式二:使用接口代理的方式,使用Mapper接口的代理方式,根据方法名和映射文件中指令ID进行绑定,动态调用相关指令信息 /* * StudentDao 是一个接口 * StudentDao dao 是StudentDao的一个实例、对象 必然存在一个实现了StudentDao接口的类 * 该类是程序自动创建的一个实现类。这种方式就是动态代理 * // 实现了接口 :则存在接口中的方法,可以获取到方法的声明 方法体则根据 * 接口全路径(包名+接口名)+方法名从Configuration的mappedStatements的Map中去根据它查找对应的 * MappedStatement ,然后根据指令类型,调用相应的执行器。执行相关代码 * */ //可以看到生成的动态代理类 将代理生成的文件 进行保存 //System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); StudentDao dao = sqlSession.getMapper(StudentDao.class); List<Student> students = dao.listStudent(); //===================================================================================== System.out.println(students); sqlSession.close(); }
执行过程简单分析
- 解析xml配置文件通过
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
得到----》configuration里面封装了configuration中的所有配置信息,其中configuration封装了一个Map类型的protected final Map<String, MappedStatement> mappedStatements;
mappedStatements变量 - 根据configuration中将mapper映射文件信息进行了解析,将mapper文件中的指令通过key和value进行存储到mappedStatements的变量Map容器中,key是由mapper中的namespace加指令id组成然后作为map容器的key,将每个指令标签封装成MappedStatement对象,MappedStatement中有sqlCommandType:指令类型和sqlSoure:解析后的sql语句,两个变量,将MappedStatement作为value。
- 创建sqlSession会话对象 ---》sqlSessionFactory.openSession();
- 不使用动态代理的话,就调用sqlSession会话对象的执行sql方法,如执行selectList
List<E> selectList(String statement)
就需要传入statement,传入map容器中的key,获取对应map容器的value得到MappedStatement对象,MappedStatement中已经定义好了参数类型、返回结果类型和原生sql - 获取原生sql参数类型 返回结果的类型,根据参数类型,原生sql可以内部预处理sql将sql发送给数据库
- 接收sql执行结果,将其封装成为对应的返回类型。
日志配置Log4j
日志,就是将程序运行时,一些关键信息进行保存文件中的操作.当问题发生时,是无法观察控制台,将关键的信息保存在文件,便于后期检查。
日志分为一下级别:
- debug调试模式时进行记录的调试信息 debug级别最低
- info具体信息 info级别高于DEBUG
- error 发生异常时可以进行记录的方法 error级别最高
1、引入pom依赖
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
2、创建日志配置文件
注意:文件名一定要是log4j.properties
,不然无法找到
# 全局日志配置 log4j.rootLogger=DEBUG, stdout,D,E # MyBatis 日志配置 log4j.logger.org.mybatis.example.BlogMapper=TRACE # 控制台输出 log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n ### 输出DEBUG 级别以上的日志到=E://logs/error.log ### log4j.appender.D = org.apache.log4j.DailyRollingFileAppender log4j.appender.D.File = E://logs/log.log log4j.appender.D.Append = true log4j.appender.D.Threshold = DEBUG log4j.appender.D.layout = org.apache.log4j.PatternLayout log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n ### 输出ERROR 级别以上的日志到=E://logs/error.log ### log4j.appender.E = org.apache.log4j.DailyRollingFileAppender log4j.appender.E.File =E://logs/error.log log4j.appender.E.Append = true log4j.appender.E.Threshold = ERROR log4j.appender.E.layout = org.apache.log4j.PatternLayout log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
在mybatis.xml中添加配置信息(在没有其他框架整合时也可以不进行配置,会自动扫描jar包识别日志)
<settings> <setting name="logImpl" value="LOG4J"/> </settings>
#和$的区别
mybatis中会将sql进行解析,解析分为两种:
- 静态sql 直接将参数进行拼接
- 动态sql 使用?占位符,替代参数
这两种解析方式是根据${}和#{}进行区分的
${}是静态sql,#{}是动态sql它们都能传递参数,静态sql它不能防止sql注入问题,而动态sql使用的是PreparedStatement进行参数预处理,可以防止sql注入。${}静态sql能做的#{}动态sql都能做。
那为什么还有${}静态sql呢?
因为PreparedStatement预处理的本质是将参数转换为字符串,当做参数字符串处理。所以如果参数是一个特殊的关键字。例如:数据库名,表名,函数名,内置关键字,使用预处理转为字符串就无效了,所以只能使用${}静态sql进行字符串拼接处理。
多个参数问题
当mybatis传递参数存在多个时,mybatis支持三种方案:
- argx 形式:arg 表示参数,x表示参数索引
- paramX形式: param表示参数,x表示第几个参数
- 使用注解为参数取别名 @Param
分页查询
三种方式:
- limit拼接sql方式
- mybatis中,提供了一个RowBounds这个类,用于进行分页查询数据,但这个类是在上述通过指令ID第一种方式执行sql时才能使用(已经不推荐使用这种方式执行sql),所以也不用这种方式分页查询
- 我们使用插件进行分页查询PageHelper
使用PageHelper分页
- 导入pom依赖
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.2.0</version> </dependency>
- 在mybatis核心配置文件中开启PageHelper插件
<plugins> <!-- 在mybatis 为了让开发者能够更好扩展mybatis功能 ,mybatis提供了插件接口 这样开发者可以通过实现接口 重写方法,对mybatis 功能进行扩展(通过拦截进行扩展) --> <!-- 分页插件 :PageHelper --> <!-- 配置分页插件 --> <plugin interceptor="com.github.pagehelper.PageInterceptor" /> </plugins>
-
在代码显示声明后面紧邻(一次)查询语句需要使用分页
在PageHelper,每次开启分页,只对最近的一次查询有效。如果要多次分页查询,需要多次开启。
PageHelper是利用,拦截器原理,拦截最近的一次查询,然后在其基础拼接分页信息。PageHelper只对最近一次查询有效,这种模式,对本身的程序没有侵入,所以,本身程序该怎么写还是怎么写。
SqlSession session = SqlSessionUtil.getSession(); //获取UserMapper接口的具体代理对象 UserMapper mapper = session.getMapper(UserMapper.class); //开启分页 2 页码 5 每页数据条数 Page<User> userPage = PageHelper.startPage(2, 5); //进行查询 PageHelper 会对查询进行拦截处理 将将要进行的查询语句进行分页处理 mapper.selectAll(); System.out.println("总页数:"+userPage.getPages()); System.out.println("页码:"+userPage.getPageNum()); System.out.println("每页条数:"+userPage.getPageSize()); System.out.println("总条数:"+userPage.getTotal()); List<User> users = userPage.getResult(); users.forEach((u) -> System.out.println(u)); session.commit(); session.close();
动态sql
- if:进行条件判断
- foreach:循环
- choose:if else
- set:取代set标签
- trim:前后缀处理标签
- where:当有多个sql条件时,需要处理and关键字问题
<resultMap id="BASE_RESULT" type="com.powernode.domain.Student"> <id column="id" property="id" /> <!--<result column="stId" property="id" />--> <result column="name" property="name"/> </resultMap> <!-- if 标签 如果 test 里面表达式结果为 true 则进行包裹的代码,外面包裹 标签where的作用是,里面的if判断条件如果都不满足那么where则不会进行拼接, 如果where标签中第一个满足条件的if,那么会在这个if里面的sql语句前拼接一个where, 后面还有满足的条件就不会在拼接where了。 --> <select id="selectByParam" resultMap="BASE_RESULT" > select id,name,age,sex,address,birth from student <where> <if test="name != null and name != ''"> and name like concat('%',#{name},'%') </if> <if test="minAge != null "> and age >= #{minAge} </if> <if test="maxAge != null "> and #{maxAge} > age </if> <if test="sex != null and sex != '' "> and sex = #{sex} </if> <if test="minBirth != null "> and birth between #{minBirth} and #{maxBirth} </if> </where> </select> <!-- choose标签 choose when的就是if if else 的作用,choose标签相当于if when相当与else if 如果choose标签中的when有一个满足则其他的when就都不会执行了。如果when都不满足则会 执行otherwise标签中的内容 --> <select id="selectByAge" resultMap="BaseResultMap"> select * from user where <choose> <when test="age > 19"> age > 19 </when> <when test="age > 18"> age > 18 </when> <otherwise> 18 > age </otherwise> </choose> </select> <!-- foreach 批量添加和删除会使用到foreach标签:循环 collection:要循环的集合,容器 item:变量名 separator:每个元素使用什么分割 open:以什么开始 close:以什么闭合 --> <insert id="batchInsert"> <if test="students != null and students.size() > 0"> insert into student values <foreach collection="students" item="st" separator=","> (0,#{st.name},#{st.age},#{st.sex},#{st.address},#{st.birth}) </foreach> </if> </insert> <update id="batchDelete"> delete from student where id <foreach collection="ids" item="id" open="in (" close=")" separator=","> #{id} </foreach> </update> <update id="updateById" parameterType="map"> update student set name = #{name},age= #{age},sex=#{sex},address=#{address},birth = #{birth} where id = #{id} </update> <!-- set set标签 取代sql的set关键字 后面是各个字段使用逗号分隔,set标签可以去掉最后一个逗号 --> <update id="updateById2" parameterType="Student"> update student <set> <if test="student.name != null and student.name != ''"> name = #{student.name} , </if> <if test="student.age != null "> age= #{student.age} , </if> <if test="student.sex != null and student.sex != ''"> sex=#{student.sex} , </if> <if test="student.address != null and student.address != ''"> address=#{student.address} , </if> <if test="student.birth != null "> birth=#{student.birth} , </if> </set> where id = #{student.id} </update> <!-- trim : 前后缀处理标签 prefix : 前缀 prefixOverrides : 去掉指定前缀 suffix : 后缀 suffixOverrides: 去掉指定后缀 --> <update id="updateById3" parameterType="Student"> update student <trim prefix=" set " suffixOverrides=","> <if test="student.name != null and student.name != ''"> name = #{student.name} , </if> <if test="student.age != null "> age= #{student.age} , </if> <if test="student.sex != null and student.sex != ''"> sex=#{student.sex} , </if> <if test="student.address != null and student.address != ''"> address=#{student.address} , </if> <if test="student.birth != null "> birth=#{student.birth} , </if> </trim> where id = #{student.id} </update>
多表查询
多对一
需求:查询每个学生所对应的老师
- 定义两个表:stu学生表和teacher老师表
CREATE TABLE `stu` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(11) NOT NULL, `age` int(11) NOT NULL, `weight` int(11) NOT NULL, `address` varchar(20) NOT NULL, `tid` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4; CREATE TABLE `teacher` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
学生表中的tid关联的是老师表中的id,使用id和tid将每个学生对应的老师信息进行关联
-
定义实体类
学生表(下面会进行扩展)
@Data @AllArgsConstructor @NoArgsConstructor public class Stu { private Integer id; private String name; private Integer age; private Integer weight; private String address; private Integer tid; }
老师表
@Data @AllArgsConstructor @NoArgsConstructor public class Teacher { private Integer id; private String name; }
-
定义接口和mapper
因为我们要根据学生表中的tid数据对应查询老师表中的姓名,所以要对学生表进行扩展,添加tname则一个字段,直接修改类是不严谨的所以我们通常在创建一个学生类的扩展类StuExt类,继承Stu类
@Data @AllArgsConstructor @NoArgsConstructor public class StuExt extends Stu{ private String tname; }
定义接口查询学生类信息
List<StuExt> selectByAll();
编写mapperxml(重点)
- 下面是针对字段是普通的类型,而不是对象类型
<resultMap id="BaseResultMap" type="com.zll.pojo.Stu"> <!--@mbg.generated--> <!--@Table stu--> <id column="id" property="id" /> <result column="name" property="name" /> <result column="age" property="age" /> <result column="weight" property="weight" /> <result column="address" property="address" /> </resultMap> <resultMap id="StuByTeacher" extends="BaseResultMap" type="com.zll.pojo.StuExt"> <result property="tname" column="tname"/> </resultMap> //首先使用我们sql的连表查询sql语句编写 <select id="selectByAll" resultMap="StuByTeacher"> select s.id,s.name,s.age,s.weight,s.address,t.name as tname from stu s inner join teacher t on(s.tid=t.id) </select>
在有些场景下我们需要的有可能不是一个表中的某一两个字段信息,需要大量的信息,以上这样写就很麻烦了,我们可以直接在Stu类中定义一个teacher对象。
-
重新定义stu类
@Data @AllArgsConstructor @NoArgsConstructor public class Stu { private Integer id; private String name; private Integer age; private Integer weight; private String address; private Integer tid; private Teacher teacher;//多了一个teacher对象 } -
编写接口方法
List<Stu> selectByAll2(); -
编写mapperxml
- 按照查询嵌套处理(子查询)
<resultMap id="BaseResultMap" type="com.zll.pojo.Stu"> <id column="id" property="id" /> <result column="name" property="name" /> <result column="age" property="age" /> <result column="weight" property="weight" /> <result column="address" property="address" /> <result column="tid" property="tid"/> <!-- //还有一个属性是一个类所以不能使用result了,要使用association来表示多对一 //property表示类中的属性名,javaType表示类型,select是要执行的对应二次查询的sql语句,column是 要传入对应查询的参数.如:根据id查询老师信息 <select id="listTeacher" resultType="Teacher"> select * from teacher where id=#{id} </select> --> <association property="teacher" javaType="com.zll.pojo.Teacher" select="com.zll.mapper.TeacherMapper.listTeacher" column="tid"/> </resultMap> <select id="selectByAll2" resultMap="BaseResultMap"> select * from stu; </select> - 按照结果嵌套处理(联表查询)
<resultMap id="StuByTeacher" type="com.zll.pojo.Stu"> <id column="id" property="id" /> <result column="name" property="name" /> <result column="age" property="age" /> <result column="weight" property="weight" /> <result column="address" property="address" /> <association property="teacher" javaType="com.zll.pojo.Teacher"> <result column="tname" property="name"/> </association> </resultMap> <select id="selectByAll2" resultMap="StuByTeacher"> select s.id,s.name,s.age,s.weight,s.address,t.name as tname from stu s inner join teacher t on(s.tid=t.id) </select>
一对多
比如:一个老师拥有多个学生
-
实体类修改
//老师类 @Data @AllArgsConstructor @NoArgsConstructor public class Teacher { private Integer id; private String name; private List<Stu> stus; } //学生类 @Data @AllArgsConstructor @NoArgsConstructor public class Stu { private Integer id; private String name; private Integer age; private Integer weight; private String address; private Integer tid; } -
编写接口和mapperxml
需求:查询一个或所有的老师所对应的学生信息,
接口
List<Teacher> listByTeacher(@Param("tid") Integer id); Student selectStudent(@Param("tid") Integer id); -
mapperxml
- 使用结果嵌套处理
<resultMap id="teacherStu" type="Teacher"> <result property="id" column="tid"/> <result property="name" column="tname"/> <collection property="stus" ofType="com.zll.pojo.Stu"> <result column="sname" property="name"/> </collection> </resultMap> <select id="listByTeacher" resultMap="teacherStu"> select t.id as tid,t.name as tname,s.name as sname from stu s inner join teacher t on(s.tid=t.id) <if test="tid !=null"> and t.id=#{tid} </if> </select> - 查询嵌套处理
<select id="selectStudent" resultType="Stu"> select * from stu where tid=#{tid} </select> <resultMap id="teacherStu2" type="Teacher"> <collection property="stus" ofType="com.zll.pojo.Stu" javaType="ArrayList" select="com.zll.mapper.StuMapper.selectStudent" column="id"/> </resultMap> <select id="listByTeacher1" resultMap="teacherStu2"> select * from teacher where id=#{tid} </select>
总结
- 关联-association【多对一】
- 集合-collection【一对多】
- javaType & ofType
- javaType用来指定实体类
- ofType用来映射到List或集合中的pojo类型,泛型中的约束类型
缓存
mybatis有一级缓存和二级缓存
一级缓存
一级缓存是指sqlSession级别的缓存,在同一个SqlSession,同样的SQL语句只会执行一次,不是第一次执行的SQL会从缓存中获取数据。
二级缓存
二级缓存: 是指SqlSessionFactory级别的缓存,在同一个SqlSessionFactory中,同样的SQL语句,只会执行一次,不是第一次执行的SQL会从缓存中获取数据。
二级缓存需要在映射文件中,开启缓存:
注意
不论一级缓存还是二级缓存,否是JVM中缓存。当服务器有多台时,缓存容易发生无效,数据发生了更新。缓存没有更新。在实际使用,如果数据可能发生比较频繁的更新不建议使用mybatis。
作用域和生命周期
SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)