mybatis学习笔记
什么是mybatis?有什么作用?
mybatis是一个工作在数据持久层的框架。它的出现是为了解决我们程序员操作数据库是需要花大量代码来实现对数据库的操做简单地来说就是他帮我们完成了对jdbc代码,数据库接口类,pojo类的书写,我们直接拿来用就行了。实现了真正意义上的sql代码和我们的程序分离,使得我们的程序更加的易于维护。
第一个mybatis程序
搭建环境
maven 、junit、mysql驱动,mybatis依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<!-- <scope>test</scope>-->
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
</dependencies>
配置mybatis配置文件
通过读取这个文件,我们就可以创建一个SqlSessionFactoryBuilder实例,然后通过这个实例获取SqlSessionFactory
编写工具类(用来获取SqlSession)
package utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class MyBatisSession {
private static SqlSessionFactory sqlSessionFactory;
static
{
String resource = "mybatis-config.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
public static SqlSession getSqlSession()
{
return sqlSessionFactory.openSession();
}
}
测试类
注意事项:
1.在这个项目里面我们需要在项目的pom文件里配置资源过滤,好让不在resource目录下的资源文件能自动生成在target目录里面
<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>
2.我们的实体类的属性名称和我们数据库的字段名称一定要一一对应(一个符号都不能错)不然我们就不能将数据库中的数据读取到实体类里面,造成查询出来的结果为空
CRUD
代码:
package dao;
import pojo.Users;
import java.util.List;
public interface UserMapper {
//查询全部用户
List<Users> getUsers();
//添加用户
int addUser(Users user);
//更改用户
int updateUser(Users user);
//删除指定用户
int deleteUser(int id);
//查询指定用户
Users getUserById(int id);
}
package dao;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import pojo.Users;
import utils.MyBatisSession;
import java.util.List;
public class UserDao_test {
当然我们也可以用map来传递我们的参数
</insert>
<insert id = "addUser2" parameterType="Map">
insert into users(u_id,u_name,u_password) values(#{id},#{name},#{pwd});
</insert>
xml的标签说明:
一个mapper对应一个在resource目录下的mybatis-config.xmlmapper映射,这里mapper就可以类比为是namespace=dao.UserMapper 的实现类
id:每个id一一对应一个dao.UserMapper下的方罚,其中的parameterType代表这个方法所需要的参数,resultType代表这个方法返回的结果
特别注意:
1.我们的sql语句书写的时候不能写注释
2.如果我们在获取SqlSessionFactory获取SqlSession的时候设置事务为自动提交那么我们对数据库的数据更改的操作将不会生效我们执行增删改的业务的时候必须要提交事务sqlSession.commit();
MyBatis的XML配置
Properties
这个就相当于配置文件,我们的environment需要一些参数来连接数据库我们就可以将我们的参数写在配置文件里面然后来动态的读取,这大大提高了对于配置的灵活性!
<properties resource = "db.properties"/>
通过这个标签可以将我们的配置文件导入进来
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
取对应的值
typeAliases
这个的作用是给类取别名,让我们每次设置resultType的时候可以直接写类了,而不是必须要写完全路径
这样是给指定的类取别名
<typeAliases>
<typeAlias type="pojo.Users" alias="users"/>
</typeAliases>
当然我们也可以给指定的包下所有的类取别名,那么这个包下的类别名就是它原来的名字只不过我们不用写全路径了,在配置resultType建议将类的首字母改成小写
<typeAliases>
<package name="pojo"></package>
</typeAliases>
setting
这个可以改变mybatis的一些运行时行为
Mapper
生命周期和作用域
SqlSessionFactoryBuilder
这个东西唯一的作用就是用来创建一个SqlSessionFactory,然后我们就不需要它了,所以它最好当做一个局部变量来使用
SqlSessionFactory(可以看做是数据连接池)
这个只需要创建一次(生命周期是从程序开始到程序结束,也就是应用程序的作用域),然后我们需要session的时候就用它来创造就行了,一般放在工具类里面,当然也可以创建多个,但是当我们遇到高并发的业务的时候就会使得程序崩溃,可能出现严重的后果
SqlSession(可以看做是连接池里面的一条连接)
当我们处理一条业务的时候我们就需要创建一个它,它不是线程安全的,当我们完成一个业务的时候就应该及时的关闭它,不然他就会一直占用我们的资源。标准的解决办法就是将它的关闭操作放在finally语句块里面
resultMap
这个与resultType对应的一个属性,当我们数据库中的字段和我们实体类属性名称不相等的时候这个时候我们查到的结果就有可能是空值,这个时候我们就用resultMap来解决这个问题。
<resultMap id = "resultmap" type="users">
<result property="u_Id" column="u_id"/>
<result property="u_Password" column="u_password"/>
<result property="u_Name" column="u_name"/>
</resultMap>
通过上面的resultMap标签我们可以将类里面的属性和数据库表里的字段一一对应
<select id="getUsers" resultMap="resultmap">
select * from users;
</select>
日志文件
这个东西能很好的记录我们程序从开始运行到结束的时候到底发生了些sm。它能帮助我们排除错误,和优化代码。我们作为一个合格的程序员,就应该要学会使用它。
LOG4J的基本使用
Log4j是
-
导包(依赖):
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency> -
在资源目录下面写LOG4J配置文件:
#将等级为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/debug.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 -
在mybatis的settings标签里面添加setting字标签:
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings> -
运行测试
sql注解(不推荐使用)
当我们的sql语句并不是很复杂的时候我们就可以使用java的注解来替代xml,下面是一个简单的事例:
1.在我们的方法上面编写sql语句注解
2.绑定接口
<mappers>
<!-- <mapper resource="dao/UserDao.xml"/>-->这里得把这个绑定方式给注释掉,不然会报绑定异常
<mapper class="dao.UserMapper"/>
</mappers>
3.测试运行
原理剖析:这里用到的是反射机制和代理模式
使用注解进行CRUD
多表连接查询
多对一的查询
也就是说学生表里面有一个字段是tid,这个是关于老师表里面的主键。站在学生的角度来讲就是多对一关系
方法一(子查询):
<select id="getStudents1" resultMap="students1">
select * from student;
</select>
<select id="getTeacherById" resultType="teacher">
select * from teacher where id = #{tid};
</select>
<resultMap id="students1" type="student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<association select="getTeacherById" property="teacher" javaType="teacher" column="tid">
</association>
</resultMap>
方法二(多表连接查询):
<select id="getStudents2" resultMap="students2">
select s.id sid,s.name sname,t.name tname,t.id tid from student s ,teacher t where t.id = s.tid;
</select>
<resultMap id="students2" type="student">
<result property="name" column="sname"/>
<result property="id" column="sid"/>
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
<result property="id" column="tid"></result>
</association>
</resultMap>
一对多的查询
也就是说站在父亲的角度下面,一个父亲对应多个儿子
1.数据库环境搭建
create table father(
id int primary key ,
`name` varchar(10)
)engine=INNODB default charset = utf8;
insert into father values(1,'yuanxipeng')
create table son(
id int primary key,
`name` varchar(10),
fid int ,
CONSTRAINT `fkfid` FOREIGN KEY (`fid`) REFERENCES `father` (`id`)
)engine = innodb default charset = utf8;
insert into son
values(1,'张三',1),
(2,'李四',1),
(3,'王二',1);
2.java实体类编写
//儿子里面有属性fid是father的主键
package com.yuan.pojo;
import lombok.Data;
方式一(子查询):
<select id="getFatherById1" resultMap="fathers1">
select * from father where id = #{fid};
</select>
<select id="selectSonByFatherId" resultType="Son">
select * from son where fid = #{id};
</select>
<resultMap id="fathers1" type="Father">
<result property="id" column="id"/>
<result property="name" column="name"/>
<collection property="sons" javaType="List" ofType="Son" column="id" select="selectSonByFatherId">
</collection>
</resultMap>
方式二(联合查询):
<select id="getFatherById2" resultMap="fathers2">
select f.id fid,f.name fname,s.id sid,s.name sname from father f,son s where s.fid = f.id and f.id = #{fid};
</select>
<resultMap id="fathers2" type="Father">
<result property="id" column="fid"/>
<result property="name" column="fname"/>
<collection property="sons" javaType="List" ofType="Son" column="f.id">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
</collection>
</resultMap>
动态SQL
搭建环境
1.创建数据库表
CREATE TABLE `blog` (
`id` varchar(50) NOT NULL COMMENT '博客id',
`title` varchar(100) NOT NULL COMMENT '博客标题',
`author` varchar(30) NOT NULL COMMENT '博客作者',
`create_time` datetime NOT NULL COMMENT '创建时间',
`views` int(30) NOT NULL COMMENT '浏览量'
) ENGINE=InnoDB DEFAULT CHARSET=utf8
2.创建数据库表对应的实体类
3.配置mybatis核心配置文件和mapper.xml文件绑定接口
4.测试代码!
在这里我们可以创建一个工具类,用来随机生成UUID(全局统一的唯一标识符)
public class GetUUID {
public static String getuuid()
{
String uuid = UUID.randomUUID().toString().replace("-","");
return uuid;
}
}
if条件语句
当我们写sql语句的时候想要动态的给where后面添加条件我们就可以使用if标签
<select id="getBlogByActive" parameterType="map" resultType="Blog">
select * from blog where true
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</select>
where标签
select * from blog where true
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
上面这种在where后面加个true写法是不规范的,为了解决这个问题所以我们需要where标签
select * from blog
<where>
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
set标签
类似于where后面的条件拼接,我们也可以在set后面进行条件拼接
<update id="updateBlogByActive" 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标签
这个就类似于我们java里面放的switch语句块。用来动态的选择拼接条件。
<select id="getBlogByChoose" 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>
true
</otherwise>
</choose>
</where>
</select>
foreach标签
对于下面这种sql我们就需要这个标签来动态的添加in()里面的条件。
select * from blog where id in(1,2,3)#这个语句如果没有foreach那么这个in里面的条件就是固定不变的
<select id="getBlogByForEach" parameterType="map" resultType="Blog">
select * from blog
<where>
<if test="ids != null">
id in<foreach collection="ids" item="id" open="(" separator="," close=")">#{id}</foreach>
</if>
</where>
;
</select>
sql片段
有些时候我们需要重复的写一些sql片段,这样会让我们的代码效率很低下,所以这个时候我们就可以通过定义sql片段然后再引用该代码段。这就类似于我们数据库里面的视图。
<!--用sql标签定义sql片段 -->
<sql id="view">
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>
<!--用include标签引用sql片段 -->
<select id="getBlogByActive" parameterType="map" resultType="Blog">
select * from blog
<where>
<include refid="view"/>
</where>
</select>
缓存(提高查询效率)
当我们访问数据库的时候,我们需要不停的建立连接在数据库里面访问数据,然后断开连接释放资源,但是这样每次都去这样的的操作,是非常的耗费时间的,特别是当访问量特别高的时候。所以为了解决的一问题,我们就在数据库和服务器之间加了一层就叫做缓存,我们将一些经常访问的数据存在内存里面,当我们再次访问相同的数据的时候我们就直接在内存里面查找我们想要的数据。
一级缓存
这个缓存存在于一次sqlsession会话之内。也就是说只要sqlsession不关闭那么缓存就不会清空。
在一个sqlsession回话内的情况:
//这里都是一个sqlsession会话所以当我们查找相同的数据的时候计算机就不会再访问数据库了而是直接访问我们的缓存。
从运行结果我可以意看见这里只是进行了一次对于数据库对的连接,并且我们查询出来的两个对象存储地址都是相同的。
当有两个sqlsession会话的时候:
而当我们打开两个sqlsession对象进行查询同一个对象的时候发现他们都各自创建了数据库连接。所以查询的两个结果比对下来的false。
另外值得我们注意的是,当我们在一个sqlsession会话里面执行有增删改操作的时候我们的缓存会自动清空,这样是为了保证数据库的ACID原则。
二级缓存
上面讲到我们的一级缓存只是作用在我们的一个sqlsession会话里面,为了弥补这一缺点所以我们的出现了二级缓存,这种缓存的作用域是在一个namespace里面,也就是在一个实体类的范围。一级缓存是默认开启的要打开二级缓存我们需要在mybatis核心配置文件里面在settings标签里面添加一个setting,然后再在绑定方法的mapper标签下面加一个cache标签,然后将我们需要用的实体类实现Serializable接口。
<setting name="cacheEnabled" value="true"/>
<mapper namespace="com.yuan.mapper.UserMapper">
<cache/>
<select id="getUserById" parameterType="_int" resultType="user"> <!--这里我们看到我们的返回的实体类是User所以我们需要让User类实现serializable接口-->
select * from users where u_id = #{u_id};
</select>
</mapper>
从这里我们可以看到当我们第一个sqlsession关闭的时候我们的第二个sqlsession会去二级缓存里面查找,找到了就不去访问数据库。
但是查询出来的两个对象的hashcode却不是相同的。
myBatis缓存原理
如果我们开了二级缓存当我们需要查询数据库里面的数据的时候我们的程序会先在二级缓存里面查找有没有我们的数据,如果没有的话那么我就会在一级缓存里面查找,找不到的话,再去访问数据库。
第三方缓存策略
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现