Mybatis
文章目录
1、简介
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
赠送官网链接:https://mybatis.org/mybatis-3/zh/index.html
2、第一个Mybatis 程序
2.1、环境搭建
数据库
CREATE DATABASE `mybatis`
use `mybatis`
CREATE TABLE `user`(
`id` int NOT NULL PRIMARY KEY ,
`name` VARCHAR(30) NOT NULL,
`pwd` VARCHAR(50) NOT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8;
insert into `user` values
(1,'张三','1233456'),
(2,'李四','1233456'),
(3,'王五','1233456')
新建项目
新建一个普通的maven项目
删除src目录 (目的作为父工程)
导入依赖
<!--导入依赖-->
<dependencies>
<!-- mysql 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<!-- mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<!-- junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
2.2、创建一个模块
-
编写Mybatis 的配置文件
在resources 下新建 Mybatis配置文件:
mybatis-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.cj.jdbc.Driver"/><!--驱动-->
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&userUnicode=true&charterEncoding=utf8&serverTimezone=UTC"/><!--url-->
<property name="username" value="TR"/><!--连接数据库的用户名-->
<property name="password" value="adminxhb"/><!--密码-->
</dataSource>
</environment>
</environments>
<!--上面几乎不用改了,这里注意每次有mapper 都需要在这里注册-->
<mappers>
<mapper resource="com/xhb/dao/UserMapper.xml"></mapper><!--mapper映射-->
</mappers>
</configuration>
可能会出现资源加载不进去的问题:因为约定大约配置(java文件不能放配置文件的)
所以如果要打破这个约定,就需要在pom.xml 中添加如下代码
<resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources>
- 编写Mybatis 工具类
/**
* Mybatis 工具类
*/
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory ;
static {
//使用Mybatis的第一步 获取 SqlSessionFactory 对象
InputStream inputStream = null;
try {
String resource = "mybatis-config.xml";
inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
// SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
2.3、编写代码
-
实体类
private int id; private String name; private String pwd; // 构造 //get/set 方法 // tostring 方法
-
DAO接口
public interface UserDao { List<User> getUserList(); }
-
接口实现类
由原来的UserDaoImp 转换为
mapper配置文件(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"> <mapper namespace="com.xhb.dao.UserDao"> <!-- 上面的namespace 对应你的Dao接口, id:接口的方法 resultType:返回的类型(接口中定义的泛型):注意是全路径,必须全路径 然后就是SQL语句 --> <select id="getUserList" resultType="com.xhb.pojo.User"> select * from Blog where id = 1 </select> </mapper>
2.4、测试
public class UserDaoTest {
@Test
public void test(){
// 第一步:获得sqlSession 对象 从工具类中获取
SqlSession sqlSession = MybatisUtils.getSqlSession();
// 执行SQL 方式一:getMapper() :推荐使用,方式二不说了。
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
}
3、CRUD
编写接口(UserMapper)
public interface UserMapper {
List<User> getUserList(); // 查询所有用户
User getUserById(int id); // 根据id查询用户
int addUser(User user); // 添加用户
int updateUser(User user); // 修改用户
int delUser(int id); // 删除用户
}
select(查询)
UserMapper.xml
<!-- 查全部-->
<select id="getUserList" resultType="com.xhb.pojo.User">
select * from mybatis.user
</select>
<!-- 根据id查-->
<select id="getUserById" resultType="com.xhb.pojo.User" parameterType="int">
select * from mybatis.user where id = #{id}
</select>
#{id}
是返回类型的 属性名。
测试
// 第一步:获得sqlSession 对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
// 执行SQL 方式一:getMapper()
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
// 关闭资源
sqlSession.close();
insert (增)
<insert id="addUser" parameterType="com.xhb.pojo.User">
insert into mybatis.user values(default ,#{name},#{pwd})
</insert>
//测试
User user = new User( "xixi", "123456");
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int rows = mapper.addUser(user);
if(rows>0){
System.out.println("插入成功");
}else{
System.out.println("插入失败");
}
//增删改 需要提交事务
sqlSession.commit();
sqlSession.close(); // 关闭资源
delete(删)
<delete id="delUser" parameterType="int">
delete from mybatis.user where id=#{id}
</delete>
update(改)
<update id="updateUser" parameterType="com.xhb.pojo.User" >
update mybatis.user
set name = #{name},pwd=#{pwd}
where id = #{id};
</update>
注意:增删改需要提交事务,查不需要。
万能Map
<update id="updateUser2" parameterType="map" >
update mybatis.user set name=#{userName},pwd=#{password} where id =#{userId}
</update>
userName
,password
,userId
是 map 集合的key
@Test
public void testUpdateUser2(){
// User user = new User(2, "qq", "12456");//
HashMap<String, Object> map = new HashMap<>();
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
map.put("userName","迪迦");
map.put("userId",1);
map.put("password","123456");
mapper.updateUser2(map);
sqlSession.commit();
sqlSession.close();
}
用Map的好处:
当一个对象的字段很多,但是SQL语句中只用到了些许字段,
那么new一个对象会麻烦 (假设有参构造:User user = new User(字段1,字段2,... 字段10)
)是不是会很烦,(难道会为了某一个用途,再增加一个特有构造方法吗? 那显然不太符合编码规范(或者增加阅读疑问)吧。
OK,既然不new 有参构造,那我new 无参构造,然后set属性:object.setXXX(Object o)
那这样总没问题吧。
好吧,问题又来了,假设有一个实体类,只能是有参构造,没有无参构造呢。假设实体类字段过多,new 的对象占用资源就大,是不是也不太合理。
那么这时候就用Map
就比较合适了(上面一大段废话,无非就是强行解释map 的好处,可以忽略…)权当只为增加一个新知识吧。
4、配置解析
https://mybatis.org/mybatis-3/zh/configuration.html:Mybatis 官方网址,可对着操作
1、核心配置文件
- Mybatis-config.xml
属性(properties)
在resources文件夹中 创建 db.properties
文件
db.properties
driver = com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/mybatis?useSSL=false&userUnicode=true&charterEncoding=utf8&serverTimezone=UTC
username = TR
password = adminxhb
注意: 之前在
environment
中使用的url 是&
转义&
, 记得改回来,小细节
mybatis-config.xml
<!-- 映入外部资源 -->
<properties resource="db.properties">
<property name="password" value="123456"/> <!--可以自己增加一些属性配置,-->
</properties>
<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>
如果引用外部资源的字段名和内部的重名了,则 外部同名属性 覆盖 内部同名属性 ,像这里的密码
123456
就不会生效
设置(settings)
cacheEnabled
缓存lazyLoadingEnabled
懒加载logImpl
日志
settings 可以设置的太多了,具体可以看文档:https://mybatis.org/mybatis-3/zh/configuration.html#settings
类型别名(typeAliases)
typeAlias
<typeAliases>
<!-- 给com。xhb.pojo.User 起别名-->
<typeAlias type="com.xhb.pojo.User" alias="User"/>
</typeAliases>
package
<typeAliases>
<!-- 给com。xhb.pojo下所有实体类起别名 默认别名是 类名(首字母小写—) 不可以修改,除非用注解-->
<package name="com.xhb.pojo"/>
</typeAliases>
如果类不多的话,可以用第一种,反之则第二种
注解
会使前两种方式失效
@Alias("hello")
public class User {
当然还有自带的一些别名,去官网看。https://mybatis.org/mybatis-3/zh/configuration.html#typeAliases
环境配置 (environments)
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-es51Hvhl-1629206460872)(D:\install\typora\images\image-20210812164140188.png)]
了解:
transactionManage
在 MyBatis 中有两种类型的事务管理器(也就是 type="[ JDBC| MANAGED ]"):dataSource
:有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):对应无连接池,连接池,普通
当然这些可能在实际开发中并不怎么用到,但是当做了解一下了, 或许在选择题,判断题,啥的问到
映射器(mappers)
使用相对于类路径的资源引用 (推荐使用)
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="com/xhb/dao/UserMapper.xml"/>
</mappers>
使用映射器接口实现类的完全限定类名
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="com.xhb.dao.UserMapper"/>
</mappers>
使用此方式的注意点:
- 接口必须和Mapper配置文件 同名
- 必须在同一个目录(包)下
将包内的映射器接口实现全部注册为映射器
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="om.xhb.dao"/>
</mappers>
使用此方式的注意点(同上):
- 接口必须和Mapper配置文件 同名
- 必须在同一个目录(包)下
其他:
貌似对于初学者没啥用的 ,想看自己看,链接已经附上
5、作用域和生命周期
作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。(老工具人了)
SqlSessionFactory
-
可以理解为线程池,只要程序还存活, 就一直存在,等待连接对象
-
没有任何理由丢弃它或重新创建另一个实例
-
SqlSessionFactory 的最佳作用域是应用作用域
-
为了确保只有这一个实例,最简单的就是使用单例模式或者静态单例模式
SqlSession
-
每个线程都应该有它自己的 SqlSession 实例
-
SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
-
每次创建后都需要关闭(一定)
-
这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中
6、解决数据库字段名和实体类属性名不一致问题
是否常听大佬 或者 老师 们说 数据库字段名 最好和 实体类属性名 要一致。
当然还有很多地方也需要按照规矩 去写代码或者建工程之类的。
比如:常见的jdk环境,是不是都让配置环境变量的时候写一个JAVA_HOME
保存路径,然后path里引入。如果这时候你没按要求,你写成小写或者其他名字的时候,发现也都可以运行,以为老师骗你的,结果你学到了Tomcat 时候,发现你的Tomcat 闪退,其原因就是开启Tomcat的startUp.bat
文件中引用的java jdk 的路径用的是JAVA_HOME
而你用的是小写,所以 找不到,所以才闪退。
所以为什么一定一定 按照规范来写代码或者建工程呢。这是有道理的。
OK ,言归正传,那要是真出现这种问题怎么办呢。应该会有小聪明会来问,那我改成一致的不就行了,,,确实,可以。不过万一公司给你的是一个老项目,写的不规范,字段不一致,那你去改表,还是改实体类呢。显然这两种方法都不可取,因为你不知道这里面牵扯了多少的代码,这只会让你的项目bug 上 再加bug。
所以我们要学习resultMap
; 看过来,看过来,肯定有小伙伴不想看一大段废话又不那么废话的废话,下面正片开始
还原现场
数据库与实体类中的字段不一致 (密码 不一致)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-29L8sXpd-1629206460876)(D:\install\typora\images\字段.png)]
结果 (密码这一栏为空)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4vwqS7cO-1629206460878)(D:\install\typora\images\image-20210813150931278.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-68JgFANU-1629206460881)(D:\install\typora\images\image-20210813151700940.png)]
解决方式一:
给数据库返回的字段取别名。
<select id="getUserById" resultType="user" parameterType="int">
select id, name, pwd as password from mybatis.user where id = #{id}
</select>
emmm… 狂神说这是小屁孩才玩的方法(自我感觉很好用(手动狗头))。
所以我们介绍解决方式二
解决方式二 :
ResultMap( 结果集映射)
<!-- 结果集映射-->
<resultMap id="userMap" type="user"> <!-- 此处的id的值和下面 resultMap是对应的 type:返回的类型-->
<result column="id" property="id"/> <!-- 字段映射 column :数据库字段 propertry:实体类属性-->
<result column="name" property="name"/>
<result column="pwd" property="password"/>
</resultMap>
<select id="getUserById" resultMap="userMap" parameterType="int">
select id, name, pwd from mybatis.user where id = #{id}
</select>
是不是发现,emmm ,还是上面那种简单,这种方式也挺简单。
如果这个世界总是这么简单就好了。
下面,看个有操作的,前方高能!!请打起精神
resultMap
ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
7、日志
Mybatis 通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志工作委托给下面的实现之一:
- SLF4J
- Apache Commons Logging
- Log4j 2
- Log4j (掌握)
- JDK logging
log4j使用
导包
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
log4j.properties
#将等级为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/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
核心配置文件中
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
static Logger logger = Logger.getLogger(TestDao.class); //阿帕奇下的包 别导错了
@Test
public void testLog4j(){
logger.info("进来了info:haha"); // 用于提示信息
logger.debug("debug:haha"); // 调试信息
logger.error("err:haha"); // 错误信息 给你分好了等级
}
8、分页
limit分页
1、编写接口 (UserMapper)
//分页查询
List<User> getUserListByLimit(Map<String,Integer> map);
2、mapper 映射
<!-- 结果集映射-->
<resultMap id="userMap" type="com.xhb.pojo.User">
<result column="id" property="id"/>
<result column="name" property="name"/>
<result column="pwd" property="password"/>
</resultMap>
<!-- 因为上次改了实体的属性,所以这用一下结果集映射-->
<select id="getUserListByLimit" resultMap="userMap" >
select * from mybatis.user limit #{startIndex},#{offset}
</select>
3、测试
@Test
public void testLimit(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String,Integer> map = new HashMap<String,Integer>();
map.put("startIndex",1);
map.put("offset",3);
List<User> list = mapper.getUserListByLimit(map);
for (User user : list) {
System.out.println(user);
}
sqlSession.close();
}
RowBounds 分页
1、编写接口
// RowBounds 分页查询
List<User> getUserListByRowBounds();
2、Mapper映射
<select id="getUserListByRowBounds" resultMap="userMap">
select * from mybatis.user
</select>
3、测试
@Test
public void testRowBounds(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
RowBounds rowBounds = new RowBounds(2, 2);
List<User> userList = sqlSession.selectList("com.xhb.dao.UserMapper.getUserListByRowBounds",null,rowBounds);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
PageHelp 分页插件
官方参考文档:https://pagehelper.github.io/docs/
9、使用注解开发
使用注解只适用于简单的sql语句。如果复杂还是推荐使用``xxxMapper.xml `配置文件
当然Mybatis 一般不适用注解。。。
1、编写接口
public interface UserMapper {
@Select("select * from user")
List<User> getUsers();
}
2、mybatis-config.xml 配置mapper(没有xml配置了,用的类映射)
<mappers>
<mapper class="com.xhb.dao.UserMapper"/>
</mappers>
3、测试
@Test
public void testzj(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.getUsers();
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
注解增删改查
前提:在mybatis-config.xml
中配置了Mapper
// 多个基本类型的参数要使用param 注解
@Insert("insert into user(id,name,pwd) values(#{id},#{name},#{password})")
int addUser(User user);
// 一个参数 可以不用@param 注解
@Delete("delete from user where id=#{id}")
int deleteUserById(int id);
// 对象的话就不用 声明@Param 注解了
@Update("Update user set name=#{name},pwd =#{password} where id = #{id}")
int updateUserById(User user);
// 多个基本类型的参数要使用 @Param 注解
@Select("select * from user where id =#{uid} and name=#{uname}")
User getUserByIdAndName(@Param("uid") int id ,@Param("uname") String name);
测试
insert(增)
@Test
public void testAddUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.addUser(new User(22,"tt","123456"));
sqlSession.commit();
sqlSession.close();
}
delete(删)
@Test
public void testDeleteUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.deleteUserById(1231);
sqlSession.commit();
sqlSession.close();
}
update(改)
@Test
public void testupdateUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int rows = mapper.updateUserById(new User(2,"qqwwqq","123456"));
System.out.println(rows);
sqlSession.commit();
sqlSession.close();
}
select(查)
@Test
public void testGetUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserByIdAndName(2, "qq");
System.out.println(user);
sqlSession.close();
}
关于@ param () 注解
- 基本类型的参数或者String类型,需要加上
- 引用类型不需要加
- 如果只有一个基本类型的话,可以忽略。为了养成习惯,也可以加上
- 在sql语句中引用的就是注解里的属性
#{注解的属性名}
#{},${} 区别
- #{} 预编译的
- ${} 不是预编译的
10、Lombok
使用
1.安装Lombok插件
File–> Settings —> Plugins
2.导入依赖
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
@Data: 无参构造,get,set,tostring ,hashCode ,equals
@AllArgsConstructor:有参构造
@NoArgsConstructor:无参构造
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Getter
private Integer id; // 只是这个字段有get方法,前期去掉上面的@Data
private String name;
private String password;
}
放在类上是所有字段;放在属性上,是单一字段
11、多对一
关联:多个学生关联了一个老师
集合:一个老师 集合了一群学生
1、环境搭建
sql 建表 填充数据
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
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (1, '小明', 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (2, '小红', 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (3, '小张', 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (4, '小李', 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (5, '小王', 1);
resources 下新建Mybatis核心配置文件,数据库连接属性
mybatis-config.xml
,db.properties
db.properties
driver = com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/mybatis?useSSL=false&userUnicode=true&charterEncoding=utf8&serverTimezone=UTC
username = TR
password = adminxhb
mybatis-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>
<!-- 映入外部资源-->
<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 class="com.xhb.dao.StudentMapper"/>
</mappers>
</configuration>
提取公用工具类MybatisUtils
package com.xhb.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;
/**
* Mybatis 工具类
*/
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory ;
static {
//使用Mybatis的第一步 获取 SqlSessionFactory 对象
InputStream inputStream = null;
try {
String resource = "mybatis-config.xml";
inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
// SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句
// openSession(true) 开启事务自动提交
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
pojo
@Data
public class Student { // 学生类
private int id;
private String name;
// 学生需要关联一个老师
private Teacher teacher;
}
public class Teacher { // 老师类
private int id;
private String name;
}
测试
@Test
public void testStudent(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> studentList = mapper.getStudentList();
for (Student student : studentList) {
System.out.println(student);
}
sqlSession.close();
}
2、子查询
select s.id,s.name,t.name from student as s ,teacher as t where s.tid = t.id
<!-- 连表查询 学生 和 老师-->
<select id="getStudents" resultMap="studentTeacher">
select * from student
</select>
<resultMap id="studentTeacher" type="student">
<association property="teacher" column="tid" select="getTeachers"/>
</resultMap>
<select id="getTeachers" resultType="teacher">
select * from teacher where id =#{id}
</select>
测试
@Test
public void testStudent(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> studentList = mapper.getStudents();
for (Student student : studentList) {
System.out.println(student);
}
sqlSession.close();
}
3、连表查询
<!-- 连表查询方式二-->
<select id="getStudents2" resultMap="st">
select s.id as sid ,s.name as sname,t.name as tname from student as s , teacher as t where s.tid = t.id
</select>
<resultMap id="st" type="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
测试结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jqH1zfPu-1629206460890)(D:\install\typora\images\image-20210816111133742.png)]
因为上面我们只查了老师的名字,所以id 并没有(那是int类型的默认值)
12、一对多
一个老师有多个学生,对于老师而言,就是一对多
环境搭建
和刚才一样
需要改的是实体类
实体类
@Data
public class Teacher {
private int id;
private String name;
// 一个老师拥有多个学生
private List<Student> students;
}
@Data
public class Student {
private int id;
private String name;
private int tid;
}
联表查询
<!-- 根据id 查询老师-->
<select id="getTeacherById" resultMap="teacherStu">
select t.id as tid,t.name as tname,s.name as sname
from teacher as t ,student as s
where t.id = s.tid and t.id = #{tid}
</select>
<resultMap id="teacherStu" type="teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<collection property="students" ofType="student">
<result property="name" column="sname"/>
</collection>
</resultMap>
测试
@Test
public void testGetTeacher(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacherById(1);
System.out.println(teacher);
sqlSession.close();
}
13、动态SQL
还记得被SQL 语句拼接的痛苦吗,现在动态SQL 来了,mama 再也不用担心我被SQL语句拼接所支配了
环境搭建
- 导入依赖(
Lombok
,mysql-connector-java
,mybatis
,junit
) 数据库驱动,Mybatis 的库必须导,其他选择性导入 - Mybatis 配置文件(
mybatis-config.xml
和db.properties
) - 提取工具类(
mybatisUtils
) - 编写实体类(
Blog
) - 编写接口(
BlogMapper
) - 编写接口对应的Mapper(
BlogMapper.xml
) 放在resources
的mapper(新建)
文件夹下
IF
实现的功能
如果map中没参数则查询所有博客,如果传入title
参数,则SQL语句增加and title = #{title}
根据标题查博客…
<!-- 动态sql if 查询博客-->
<select id="getBlogListByIf" parameterType="map" resultType="blog">
select * from blog where 1=1
<if test="title!=null">
and title = #{title}
</if>
<if test="author!=null">
and author = #{author}
</if>
</select>
@Test
public void testIf(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
// map.put("author","哈哈");
// map.put("title","mybatis");
List<Blog> list = mapper.getBlogListByIf(map);
for (Blog blog : list) {
System.out.println(blog);
}
结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iENM3USK-1629206460892)(D:\install\typora\images\动态sql.png)]
choose (when otherwise)
相当于就是java的switch
switch(xxx){ //choose 就相当于switch 这个框
case 0:xxxx;break;//when 就相当于每一个case
case 1:xxx;break;
default:xxx;break;//otherwise 相当于default
}
<!-- 动态sql where choose when otherwise 查询博客-->
<select id="getBlogListByChoose" parameterType="map" resultType="blog">
select * from blog
<where>
<!-- 分支结构 和java的switch一样的,只能选择其一 ,emmm 不存在java的switch穿透-->
<choose>
<when test="title!=null">
title = #{title}
</when>
<when test="author!=null">
author = #{author}
</when>
<otherwise >
views >= 10
</otherwise>
</choose>
</where>
</select>
应该是没有java中switch的穿透概念的。
set
<!-- 动态sql set 查询博客-->
<update id="updateBlogBySet" parameterType="map">
update blog
<set>
<if test="title!=null">
title = #{title},
</if>
<if test="author!=null">
author = #{author},
</if>
</set>
where id = #{id}
</update>
@Test
public void testSet(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
map.put("author","嘻嘻");
map.put("id","11e81df322be44b8bd0a6aecf6417ba3");
mapper.updateBlogBySet(map);
sqlSession.commit();
sqlSession.close();
}
多了他可以帮你省,少了就报错
SQL片段
重复性比较高的语句提取出来,封装,就形成SQL片段,提高复用
<sql id="if_title_author">
<if test="title!=null">
and title = #{title}
</if>
<if test="author!=null">
and author = #{author}
</if>
</sql>
<!-- 动态sql if 查询博客-->
<select id="getBlogListByIf" parameterType="map" resultType="blog">
select * from blog
<where>
<include refid="if_title_author"></include> <!-- include 标签引用上面sql片段 -->
</where>
</select>
注意:最好基于单表定义sql片段,对于复杂的SQL 采用sql拼接可能对前后有冲突之类的,并不能提高复用,所以单表好。
foreach
<!-- 查询id 为 1,3的博客-->
<select id="getBlogByIn" parameterType="list" resultType="blog">
select * from blog where id in
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
<!-- 这里取的id就是item 的id -->
</foreach>
</select>
@Test
public void testForeach(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
ArrayList arrayList = new ArrayList();
arrayList.add(1);
arrayList.add(3);
List<Blog> blogByIn = mapper.getBlogByIn(arrayList);
for (Blog blog : blogByIn) {
System.out.println(blog);
}
sqlSession.close();
}
14、Mybatis缓存
Mybatis包含一个非常强大的查询缓存特性,他可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
- Mybatis 系统中默认定义了两种缓存:一级缓存 和 二级缓存
- 默认情况下,只有一级缓存开启。(SQLSession级别的缓存,也称为本地缓存)
- 二级缓存需要手动开启和配置,他是基于namespace 级别的缓存。
- 为提高扩展性,Mybatis定义了缓存接口Cache。 实现Cache接口自定义二级缓存
一级缓存
测试前的准备工作,开启日志
mybatis-congif.xml
<!-- 设置日志-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
测试结果
注意对比两张图的日志输出
第二张图 明显是查了两遍,而第一张图只查了一遍
缓存失效情况:
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不定时进行刷新(也就是说,没有刷新间隔)。
- 手动清除缓存
sqlSession.clearCache();
二级缓存
-
二级缓存也叫全局缓存,一级缓存的作用域太低了,所以诞生了二级缓存。
-
二级缓存基于namespace 级别的缓存
工作机制
一个会话查询一条数据,放在一级缓存中,当会话关闭了,对应的一级缓存也没了,数据被保存到了二级缓存中,新的会话查询时,就可以从二级缓存中获取
使用:
1、开启缓存
虽然默认值是true,可以不用写,为了增加阅读性还是可以写的
<!-- 开启缓存-->
<setting name="cacheEnabled" value="true"/>
2、在要使用的mapper.xml 中开启(这样就可以用了)
<cache/>
也可以配置一些参数
<cache
eviction="FIFO" <!--清除策略 FIFO:先进先出-->
flushInterval="60000" <!-- ,每隔 60 秒刷新-->
size="512" <!-- 最多可以存储结果对象或列表的 512 个引用-->
readOnly="true"/> <!-- 返回的对象被认为是只读的-->
可用的清除策略有:
LRU
– 最近最少使用:移除最长时间不被使用的对象。FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
3、测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JpM3DevQ-1629206460893)(D:\install\typora\images\image-20210817171356002.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sc0rRX2R-1629206460894)(D:\install\typora\images\缓存2-1629192410201.png)]
扩展
在sql 标签中能定义时候使用,刷新缓存
遇到了问题:
开启二级缓存后报错 实体类没有序列化?
那么在 实体类中实现序列化接口 或者 <cache readOnly="true"/>
小结一下:
-
一级缓存的作用域在sqlSession中
-
二级缓存的作用域在Mapper.xml 的namespace 中
-
二级缓存的数据一定是一级缓存关闭前存进去的(也就是先有一级缓存才有二级缓存)
回顾Mybatis 的一生//todo
很多年后,你想起了陪你一起走过风风雨雨的Mybatis。你陷入了回忆…
你想起你的第一节课,什么是Mybatis 。
老师说:Mybatis 是一个可以自定义SQL,存储过程和高级映射的持久层框架?并且能摒除大部分的JDBC代码、手工设置参数和结果集映射。
你留下了疑问?能摒除大部分JDBC代码?
于是老师带着你来探寻其中的真理了…
老师让你配置好你的环境,有什么呢?
-
数据库(表)是不能少的
-
新建普通maven项目,删除src目录(作为父工程),新建module(子工程)
-
导入依赖(
mysql-connector-java
,mybatis
,lombok
,junit
) 前两个必须导入<dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId><!--mysql 驱动--> <version>8.0.11</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId><!-- mybatis--> <version>3.5.2</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId><!--junit--> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId><!--lombok--> <version>1.18.20</version> </dependency> </dependencies>
-
编写mybatis核心配置文件
mybatis-config.xml
db.properties
<?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.cj.jdbc.Driver"/><!--驱动--> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&userUnicode=true&charterEncoding=utf8&serverTimezone=UTC"/><!--url--> <property name="username" value="TR"/><!--连接数据库的用户名--> <property name="password" value="adminxhb"/><!--密码--> </dataSource> </environment> </environments> <!--上面几乎不用改了,这里注意每次有mapper 都需要在这里注册--> <mappers> <mapper resource="com/xhb/dao/UserMapper.xml"></mapper><!--mapper映射--> </mappers> </configuration>
driver = com.mysql.cj.jdbc.Driver url = jdbc:mysql://localhost:3306/mybatis?useSSL=false&userUnicode=true&charterEncoding=utf8&serverTimezone=Asia/Shanghai username = TR password = adminxhb
准备工作终于做好了,老师开始教你封装JDBC 代码了,提取工具类(MybatisUtils
)
老师告诉你,首先将你的配置文件 转换成流
InputStream inputStream = null;
String resource = "mybatis-config.xml";
inputStream = Resources.getResourceAsStream(resource); //将配置文件 转换成流
然后将 资源流 通过sqlSession 工厂建造者,给你建一个sqlsession工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
然后你有了sqlsession的工厂,你就可以肆意妄为的造sqlsession了
从 SqlSessionFactory 中获取 SqlSession
SqlSession session = sqlSessionFactory.openSession()
而后得到了完整的工具类
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory ;
static {
//使用Mybatis的第一步 获取 SqlSessionFactory 对象
InputStream inputStream = null;
try {
String resource = "mybatis-config.xml";
inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(); // openSession(true) 开启事务自动提交
}
}
工具类也好了,去测试吧!
测试前提工作:编写实体类,接口,接口映射Mapper.xml
-
编写实体类
老师说:偷懒的孩子可以用 Lombok 的注解
@Data
,@AllArgsConstructor
,@NoArgsConstructor
,分别可以生成get/set ,equals,tostring ,hashCode 和 全参构造 和 无参构造 的实体类。
@Data @AllArgsConstructor @NoArgsConstructor public class User { private Integer id; private String name; private String pwd; }
-
接口
public interface UserMapper { // 查询所有用户 List<User> getUserList(); }
-
Mapper.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"> <mapper namespace="com.UserMapper"> <!--插入--> <select id="getUserList" resultType="com.User"> select * from mybatis.user </select> </mapper>
在核心配置文件中注册mapper
<mappers>
<mapper resource="com/xhb/dao/UserMapper.xml"></mapper>
</mappers>
这次真的都准备好了
@Test
public void test(){
// 第一步:获得sqlSession 对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
// 执行SQL 方式一:getMapper()
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
// 关闭资源
sqlSession.close();
}
老师说 第一个Mybatis的程序就写完了,
至于代码有些是不需要改的
比如:工具类,就可以不用动他了。核心配置文件,也几乎不需要大修改了,只是有用到某些东西的时候,往里面加几句代码就可以了,实体类这不需要动了,除非再增加其他实体类
那么要修改的就只有,接口,mapper.xml 了
接口很简单,只需要往里面添加需要的方法。
重点就是mapper.xml 了, 所有的sql语句都写在这里。
第一节课下课了,,,,
第二节课上课了,,,,
老师让你们开始练习写写CRUD
你开心的写着
第一步写接口
写接口还是比较轻松的,只需要想清楚要传递的参数,返回值类型
public interface UserMapper {
List<User> getUserList();// 查询所有用户
User getUserById(int id);// 根据id查询用户
int addUser(User user); // 添加用户
int updateUser(User user); // 修改用户
int updateUser2(Map<String,Object> map2);// 修改用户
int delUser(int id); // 删除用户
}
第二步写mapper.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">
<mapper namespace="com.xhb.dao.UserMapper">
<select id="getUserList" resultType="com.xhb.pojo.User">
select * from mybatis.user
</select>
<select id="getUserById" resultType="com.xhb.dao.pojo" parameterType="int">
select * from mybatis.user where id = #{id}
</select>
<insert id="addUser" parameterType="com.xhb.dao.pojo">
insert into mybatis.user values(default ,#{name},#{pwd})
</insert>
<update id="updateUser" parameterType="com.xhb.dao.pojo" >
update mybatis.user
set name = #{name},pwd=#{pwd}
where id = #{id};
</update>
<delete id="delUser" parameterType="int">
delete from mybatis.user where id=#{id}
</delete>
<update id="updateUser2" parameterType="map" >
update mybatis.user set name=#{userName},pwd=#{password} where id =#{userId}
</update>
</mapper>
老师教你:namespace
就是绑定你的接口
然后有很多标签 常用 insert
,delete
, update
,select
对应的就是增删改查
其中id
代表的是你接口中的方法,,,
有时候会传递参数,那么parameterType
就代表你传递参数的类型
其中属性resultType
代表是返回值类型,必须要全限定名
你觉得有些麻烦,于是老师又教了你一招
在核心配置文件中取别名(两种方式)
<typeAliases>
<!-- 给com。xhb.pojo.User 起别名-->
<typeAlias type="com.xhb.pojo.User" alias="User"/>
</typeAliases>
<typeAliases>
<!-- 给com。xhb.pojo下所有实体类起别名 默认别名是 类名(首字母小写—) 不可以修改,除非用注解-->
<package name="com.xhb.pojo"/>
</typeAliases>
@Alias("haha")
public class User {} // 注解方式取别名
然后开始写sql语句了,你不明白那个#{id}
是啥意思,就是接口传递过来变量(对象的属性)