Mybatis框架的基本知识梳理
一、原始JDBC开发存在的问题
import org.junit.Test;
import java.math.BigDecimal;
import java.sql.*;
public class JdbcTest {
@Test
public void testJdbc(){
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//1.反射加载驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取数据库连接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/java", "root", "root");
//3.编写SQL
String sql = "SELECT * FROM t_user WHERE user_id = ?";
//4.获取执行SQL的载体对象,预编译SQL
preparedStatement = connection.prepareStatement(sql);
//填充占位符
preparedStatement.setLong(1, 3);
//5.执行SQL
resultSet = preparedStatement.executeQuery();
//6.处理结果
if (resultSet.next()) {
//user_id : 结果集的列名
long userId = resultSet.getLong("user_id");
String username = resultSet.getString("username");
BigDecimal balance = resultSet.getBigDecimal("balance");
System.out.println(userId + "," + username + "," + balance );
// 数据的列取出后---》封装成对象
每个列的值要和对象属性值对象
User user =new User();
uset.setUserId(userId);
user.setUsername(username);
....
//DbUtils
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//7.释放资源
//先开后关
try {
if(null != resultSet) {
resultSet.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(null != preparedStatement) {
preparedStatement.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(null != connection) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
存在的问题
1.需要频繁的手动获取连接
2.需要手动封装查询结果集
3.需要手动释放资源
4.SQL硬编码
5.有自己封装的工具类,也有Dbutils等工具类API。但是都没有从根本上解决上面的问题
二、ORM框架
- 通过ORM框架解决JDBC存在的问题
1、ORM
- Object Relation Mapping:对象关系映射 Java对象与关系型数据库中每行数据的对应关系
Java对象 | 数据库表 |
---|---|
类名 | 表名 |
属性名 | 列名【字段名】 |
对象 | 行【记录】 |
2、框架
- 概述:就是一个半成品软件
- 需要使用框架帮我们完成JavaEE应用的开发
- 作用
- 能够帮我们快速有效的开发JavaEE应用
- 有自己对应用场景的完整解决方案
1.什么是框架
为了解决一个共性的问题(比较繁琐、但是有规律),而提供的一个解决方案。
比如:现在遇到数据层查询比较繁琐,但是有规律,交给框架去做。
框架的本质:就是jar+配置文件
jar包里封装功能,提供一些api,提供一些接口、类、方法,只负责调用,因为都给我们封装好了
配置文件:把一些不怎么变化的内容直接发到配置文件里,如果直接写在Java代码里,是需要编译的
2.框架是解决什么问题
数据层问题
3.框架的基本原理
1.sql语句的配置文件
DeptMapper.xml: 存储部门相关的sql语句
EmpMapper.xml: 存储员工相关的sql语句
2.数据层接口
DeptMapper.java 部门操作的数据层接口
EmpMapper.java 员工操作的数据层接口
3.总配置文件
总的配置文件:主要是放jdbc参数的,因为mybatis底层就是jdbc
3、程序跑通
1.导入jar
mybatis.jar +jdbc.jar
2.准备mybatis相关的配置文件
3.调用jar包里的api实现查询功能、增加功能
三、Mybatis
1.概述
-
是一款开源的、优秀的、支持定制SQL的ORM框架
-
官网:https://mybatis.org/mybatis-3/zh/index.html
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
2. 实现步骤
- 创建数据库表
- 导入依赖
- 实体类
- Mapper接口(Dao接口)
- SQL映射文件
- 全局配置文件
- 测试
3. 具体实现
- 创建数据库表
CREATE TABLE `t_user` (
`user_id` bigint(100) NOT NULL AUTO_INCREMENT COMMENT '主键',
`username` varchar(20) DEFAULT NULL COMMENT '用户名',
`password` varchar(32) DEFAULT NULL COMMENT '密码',
`nick_name` varchar(20) DEFAULT NULL COMMENT '昵称',
`is_admin` tinyint(4) DEFAULT NULL COMMENT '是否管理员 0:否 1:是',
`phone` varchar(11) DEFAULT NULL COMMENT '手机',
`gender` tinyint(4) DEFAULT NULL COMMENT '性别 0:保密 1:男 2:女',
`birth` date DEFAULT NULL COMMENT '生日',
`user_status` tinyint(4) DEFAULT NULL COMMENT '状态(是否激活) 0:否 1:是',
`user_create_time` datetime DEFAULT NULL COMMENT '创建时间',
`user_update_time` datetime DEFAULT NULL COMMENT '更新时间',
`is_delete` tinyint(4) DEFAULT NULL COMMENT '是否删除 0:否 1:是',
`is_member` tinyint(4) DEFAULT NULL COMMENT '是否会员 0:否 1:是',
`balance` decimal(20,2) DEFAULT NULL COMMENT '账户余额',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COMMENT='这是一个用户表';
insert into `t_user`(`user_id`,`username`,`password`,`nick_name`,`is_admin`,`phone`,`gender`,`birth`,`user_status`,`user_create_time`,`user_update_time`,`is_delete`,`is_member`,`balance`) values
(1,'aa','123456','666',1,'13566778899',1,'1990-07-18',0,'2021-10-15 10:56:40','2022-10-22 10:56:43',0,1,10000.00),
(2,'bb','123456','666',1,'13856789526',1,'1990-07-18',0,'2021-11-02 14:20:25','2022-11-03 09:25:58',0,1,1111.00),
(3,'cc','123456','666',1,'13512341234',0,'1990-07-18',0,'2021-11-03 10:05:37','2022-11-03 10:05:37',0,1,10000.00),
(5,'dd','123456','666',1,'13512341234',0,'1990-07-18',0,'2021-11-03 10:15:00','2022-11-03 10:15:00',0,1,10000.00),
(6,'ee','123456','666',1,'13512341234',2,'1990-07-18',0,'2021-11-03 10:40:01','2022-11-03 10:40:01',0,1,10000.00),
(7,'ff','123456','666',0,'13512341234',0,'1990-07-18',0,'2021-11-05 11:23:45','2022-11-05 11:23:45',0,1,10000.00);
- 导入依赖
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- lombok : 能够快速帮我们生成实体的getter/setter方法 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<scope>provided</scope>
</dependency>
</dependencies>
- 实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long userId;
private String username; //成员变量
private String password;
private String nickName;
private Integer isAdmin;
private String phone;
private Integer gender;
private Date birth;
private Integer userStatus;
private Date userCreateTime;
private Date userUpdateTime;
private Integer isDelete;
private Integer isMember;
private BigDecimal balance;
}
- Mapper接口
public interface UserMapper {
/**
* 查询所有
* @return
*/
List<User> selectAll();
}
- SQL(Mapper)映射文件
<?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">
<!--
mybatis代理接口开发的要求:
1.mapper映射文件的namespace 写 mapper接口的全类名
2.mapper映射文件的statementId 写 mapper接口的对应方法名
3.mapper映射文件的resultType 写 mapper接口的对应方法返回值类型。如果是集合,写泛型
4.mapper映射文件的parameterType 写 mapper接口的对应方法形参类型。高版本mybatis可以不写,但是不推荐
-->
<mapper namespace="com.gl.ssm.mapper.UserMapper">
<select id="selectAll" resultType="com.gl.ssm.pojo.User">
<!-- 原生SQL -->
SELECT * FROM t_user
</select>
</mapper>
- 全局配置文件
<?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/java"/>//数据库名称
<property name="username" value="root"/>//数据库账号
<property name="password" value="root"/>//数据库密码
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/gl/ssm/mapper/IUserMapper.xml"/>
</mappers>
</configuration>
- 测试代码
/**
* mapper接口代理测试
**/
@Test
public void test02() {
SqlSession session = null;
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
session = sqlSessionFactory.openSession();
//获取mapper代理
IUserMapper userMapper = session.getMapper(UserMapper.class);
List<User> users = userMapper.selectAll();
//mybatis能够帮我们自动完成查询结果集和实体间的映射,前提结果集列名和实体属性名相同
for (User user : users) {
System.out.println(user);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(null != session) {
session.close();
}
}
}
四、CURD操作数据库
增删改操作涉及事务,务必要提交
1.增加
- Mapper接口
/**
* 增加
* @param user
*/
void insertUser(User user);
- Mapper映射文件
<!-- void insertUser(User user);-->
<insert id="insertUser" parameterType="com.gl.ssm.pojo.User">
INSERT INTO t_user (
username,password,nick_name,is_admin,phone,gender,birth,user_status,
user_create_time,user_update_time,is_delete,is_member,balance)
VALUES
(#{username}, #{password}, #{nickName}, #{isAdmin}, #{phone}, #{gender}, #{birth}, #{userStatus}, #{userCreateTime}, #{userUpdateTime}, #{isDelete}, #{isMember}, #{balance})
</insert>
- 测试类
/**
* 增加
**/
@Test
public void test03() throws Exception {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = factory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setUsername("lucy");
user.setPassword("147852");
user.setNickName("露西");
user.setIsAdmin(0);
user.setPhone("13566778899");
user.setGender(0);
user.setBirth(java.sql.Date.valueOf("2022-10-30"));
user.setUserStatus(1);
user.setUserCreateTime(new Date());
user.setUserUpdateTime(new Date());
user.setIsDelete(0);
user.setIsMember(1);
user.setBalance(new BigDecimal(2000));
userMapper.save(user);
//提交事务
sqlSession.commit();
if(null != sqlSession) {
sqlSession.close();
}
}
1.1 主键回填
-
主键自增
- 方式一【Mapper映射文件】
<!--void saveReturnPrimaryKey(User user);--> <insert id="saveReturnPrimaryKey" parameterType="com.gl.ssm.pojo.User"> <!-- selectKey : 是指要执行的相关的SQL order: selectKey中SQL的执行顺序,after代表之后,before代表之前 resultType : selectKey中SQL的返回值类型 keyProperty : selectKey中SQL的返回值要赋值给哪个JavaBean的属性 keyColumn : selectKey中SQL的返回值对应的数据库表的列【这个值可以不写,会自动映射】 --> <selectKey order="AFTER" resultType="java.lang.Long" keyProperty="userId" keyColumn="user_id"> SELECT LAST_INSERT_ID() </selectKey> INSERT INTO t_user (username,password,nick_name,is_admin,phone,gender,birth,user_status, user_create_time,user_update_time,is_delete,is_member,balance) VALUES (#{username}, #{password}, #{nickName}, #{isAdmin}, #{phone}, #{gender}, #{birth}, #{userStatus},#{userCreateTime}, #{userUpdateTime}, #{isDelete}, #{isMember}, #{balance}) </insert>
-
方式二
<!-- void saveReturnPrimaryKey2(User user); -->
<!--
useGeneratedKeys : 是否使用主键生成策略
true:是
这种方式可以获取批量插入的主键值
-->
<insert id="saveReturnPrimaryKey2" parameterType="com.gl.ssm.pojo.User"
useGeneratedKeys="true" keyProperty="userId" keyColumn="user_id">
INSERT INTO t_user
(username,password,nick_name,is_admin,phone,gender,birth,user_status,
user_create_time,user_update_time,is_delete,is_member,balance)
VALUES
(#{username}, #{password}, #{nickName}, #{isAdmin}, #{phone}, #{gender}, #{birth}, #{userStatus}, #{userCreateTime}, #{userUpdateTime}, #{isDelete}, #{isMember}, #{balance})
</insert>
2.修改
<!--int update(User user);-->
<update id="update">
UPDATE t_user
SET PASSWORD = #{password}, nick_name = #{nickName}, user_update_time = #{userUpdateTime}
WHERE user_id = #{userId}
</update>
3.删除
<!--boolean deleteById(Long userId);-->
<delete id="deleteById" parameterType="long">
DELETE FROM t_user WHERE user_id = #{userId}
</delete>
4.主键是字符串实现主键回填
- 实体类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Stu {
private String stuId;
private String stuName;
}
- Mapper接口和映射文件
<?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.gl.ssm.mapper.StuMapper">
<!-- int saveReturnPrimaryKey(Stu stu); -->
<insert id="saveReturnPrimaryKey" parameterType="com.gl.ssm.pojo.Stu">
<selectKey order="BEFORE" keyProperty="stuId" resultType="string" keyColumn="stu_id">
SELECT REPLACE(UUID(), '-', '')
</selectKey>
insert into t_stu (stu_id, stu_name) values (#{stuId},#{stuName})
</insert>
</mapper>
五、日志
1.日志体系
- Slf4j:接口【门面】
- Log4j
- Logback
- commons-logging
2.日志作用
- 没日志:很难判断问题来源
- 有日志:一般都可以判断问题来源。
- 可以通过日志把问题错误信息给记录下来
3.使用
- 引入依赖
<!-- 日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
- 日志配置文件【log4j.properties】
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.err
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=ssm.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
log4j.rootLogger=debug, stdout, file
- 日志级别
级别 | 描述 |
---|---|
ALL LEVEL | 打开所有日志记录开关;是最低等级的,用于打开所有日志记录。 |
DEBUG | [输出调试信息;指出细粒度信息事件对调试应用程序是非常有帮助的。【开发使用】 |
INFO | 输出提示信息;消息在粗粒度级别上突出强调应用程序的运行过程。【线上使用】 |
WARN | 输出警告信息;表明会出现潜在错误的情形。 |
ERROR | 输出错误信息;指出虽然发生错误事件,但仍然不影响系统的继续运行。 |
FATAL | 输出致命错误;指出每个严重的错误事件将会导致应用程序的退出。 |
OFF LEVEL | 关闭所有日志记录开关;是最高等级的,用于关闭所有日志记录。 |
- 直接使用API即可
package com.qf.java2107.test;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoggerTest {
Logger logger = LoggerFactory.getLogger(LoggerTest.class);
@Test
public void logTest() throws Exception {
logger.debug("debug---->{}", "debug level");
logger.info("info--->{}---{}", "aa", "bb");
logger.warn("warn----->");
logger.error("error---->");
}
}
六、Mapper接口参数绑定
1.单个简单类型参数
基本数据类型及其包装类,String,int,
2.实体类型参数
JavaBean
Mapper映射文件获取参数值时,使用#{},{}中写JavaBean的属性名
3.Map入参
Mapper映射文件获取参数值时,使用#{},{}中写Map中key的名称
4.多个参数
mybatis会把参数封装成一个Map,第一个参数的key为
arg0
【param1
】,第二参数的key为arg1
【param2
】,以此类推。以上方式不推荐使用,可读性太差。
Mapper映射文件获取参数值时,使用@Param来为Mapper接口方法指定入参的参数名,使用#{}取值
七、ORM映射
把查询结果集跟JavaBean进行映射绑定
1.映射规则
-
当查询结果集的列名跟JavaBean属性名相同时,自动映射
-
当查询结果集的列名跟JavaBean属性名不相同时,需要手动映射
2.不满足映射规则
-
如果满足驼峰
- 使用别名进行映射【但是SQL太长】
<!-- User selectById(Long userId); --> <select id="selectById" parameterType="Long" resultType="com.gl.ssm.pojo.User"> SELECT * FROM t_user WHERE user_id = #{userId} </select>
- 全局配置文件【mybatis-config.xml】开启驼峰映射
<settings> <!-- 开启驼峰映射,编写的SQL就不用使用别名了 --> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>
<!-- User findByIdMapping2(Long userId); --> <select id="findByIdMapping2" parameterType="Long" resultType="com.gl.ssm.pojo.User"> SELECT user_id, username, password, nick_name, is_admin, phone, gender, birth, user_status, user_create_time, user_update_time, is_delete, is_member, balance FROM t_user WHERE user_id = #{userId} </select>
3.ResultMap
- 自定义结果集映射
- mybatis内部已经集成了结果集映射,就是Mapper映射文件的
resultType
属性。resultType
底层使用其实也是ResultMap
- resultType和resultMap最好只使用一个
- 如果满足驼峰并且不使用别名的情况下,可以使用resultType
- resultMap可以在查询结果集封装中自定义使用。连表查询只能使用resultMap
- mybatis内部已经集成了结果集映射,就是Mapper映射文件的
<!--
resultMap: 自定义结果集
id : resultMap的名称,是一个唯一标识,用于被select的resultMap属性所引用
type : 查询的结果集要映射到的实体类型
-->
<resultMap id="ResultMap" type="com.gl.ssm.pojo.User">
<!--
id : 映射主键列,只是一个标识作用,也可以用result
column : 查询的结果集的列名
property : JavaBean的属性名
-->
<id column="uid" property="userId"/>
<!-- result : 映射普通列 -->
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="name" property="nickName"/>
<result column="is_admin" property="isAdmin"/>
<result column="phone" property="phone"/>
<result column="sex" property="gender"/>
<result column="birth" property="birth"/>
<result column="user_status" property="userStatus"/>
<result column="ctime" property="userCreateTime"/>
<result column="user_update_time" property="userUpdateTime"/>
<result column="is_delete" property="isDelete"/>
<result column="is_member" property="isMember"/>
<result column="balance" property="balance"/>
</resultMap>
<!-- User findByIdUseResultMap(Long userId); -->
<select id="findByIdUseResultMap" parameterType="Long" resultMap="myResultMap">
SELECT
user_id uid, username, password, nick_name name, is_admin, phone, gender sex,
birth, user_status, user_create_time ctime, user_update_time, is_delete, is_member, balance
FROM
t_user
WHERE user_id = #{userId}
</select>
八、全局配置文件
1.properties
<!--
properties : 加载外部properties文件
resource : properties文件基于classpath的路径
-->
<properties resource="jdbc.properties" />
2.settings
<!-- 全局设置 -->
<settings>
<!-- 开启驼峰映射 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
3.typeAliases
- 自定义别名
<!-- 别名设置 -->
<typeAliases>
<!-- 单个取别名 -->
<!--<typeAlias type="com.gl.ssm.pojo.User" alias="user"></typeAlias>
<typeAlias type="com.gl.ssm.pojo.Student" alias="student"></typeAlias>-->
<!-- 批量取别名,默认别名是类名,别名不区分大小写 -->
<package name="com.gl.ssm.pojo"/>
</typeAliases>
4.plugins
4.1 分页插件概述
- 可以屏蔽底层数据库的差异,实现同一API实现不同数据库的分页功能
- https://gitee.com/free/Mybatis_PageHelper
4.2 使用步骤
- 导入依赖
//分页插件依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.10</version>
</dependency>
- 全局配置文件
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 配置方言,不配置,则使用数据库连接来判断 -->
<property name="helperDialect" value="mysql"/>
<!-- 合理化参数 -->
<property name="reasonable" value="true"/>
</plugin>
</plugins>
-
使用
- Mapper映射文件
<!-- List<User> selectAll(); --> <select id="selectAll" resultType="User"> select * from t_user </select>
- 测试代码
/** * 分页 **/ @Test public void Test() throws Exception { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //分页设置跟查询之间不要出现其他操作 //参数一:页码 //参数二:显示条数 PageHelper.startPage(3, 5); List<User> list = userMapper.selectAll(); PageInfo<User> pageInfo = new PageInfo<>(list); System.out.println("当前页:" + pageInfo.getPageNum()); System.out.println("当前页集合:" + pageInfo.getList()); System.out.println("总页数:" + pageInfo.getPages()); System.out.println("总条数:" + pageInfo.getTotal()); System.out.println("显示条数:" + pageInfo.getPageSize()); //显示条数 System.out.println("实际显示条数:" + pageInfo.getSize()); //实际显示条数 }
5.environments
- 环境配置
<!-- 环境配置 -->
<!--
default 默认使用哪个环境,这个值是指environments下的某个environment子标签的id属性
-->
<environments default="mysqldb">
<!--
mysql环境
id :就是当前环境的唯一标识,可能被environments的default引用
-->
<environment id="mysqldb">
<!--
transactionManager : 事务管理器
type : JDBC
-->
<transactionManager type="JDBC"></transactionManager>
<!--
dataSource : 数据源
type : POOLED 池化
-->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
<!-- oracle环境 -->
<!--<environment id="oracledb">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>-->
</environments>
6.mappers
- 用来加载Mapper映射文件
<!-- 加载mapper映射文件 -->
<mappers>
<!--
mapper : 加载单个mapper映射文件
resource :基于classpath路径下的mapper映射文件
class : 基于mapper接口,mybatis注解开发方式。写mapper接口全类名
-->
<!--<mapper resource="com/gl/ssm/mapper/UserMapper.xml" ></mapper>-->
<!--
批量加载mapper映射文件
要求:mapper映射文件跟mapper接口在编译后必须在同一个路径下
-->
<package name="com.gl.ssm.mapper"/>
</mappers>
九、连表查询
1.表与表之间的关系
-
数据库层面
- 一对多:部门对员工、公司对部门
- 多对多:项目跟程序员、学生跟老师
- 多对一:学生对班级、员工对部门
- 一对一:人跟身份证、旅客跟护照
-
mybatis层面
- 一对多【多对多】
- 一对一【多对一】
2.部门表跟员工表为例
- 一对多:查询部门关联查询员工
- 一对一:查询员工关联查询部门
2.1 准备工作
- 数据库表
CREATE TABLE `dept` (
`deptid` INT(11) NOT NULL AUTO_INCREMENT,
`deptname` VARCHAR(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
INSERT INTO `dept`(`deptid`,`deptname`) VALUES
(1,'研发部'),
(2,'市场部'),
(3,'财务部'),
(4,'测试部');
CREATE TABLE `emp` (
`empid` INT(11) NOT NULL AUTO_INCREMENT,
`empname` VARCHAR(20) NOT NULL,
`sex` INT(1) DEFAULT NULL COMMENT '1:男 0:女',
`birthday` DATE DEFAULT NULL,
`hire_date` DATETIME DEFAULT NULL,
`salary` INT(11) DEFAULT NULL,
`address` VARCHAR(200) DEFAULT NULL,
`deptid` INT(11) DEFAULT NULL,
PRIMARY KEY (`empid`)
) ENGINE=INNODB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8;
INSERT INTO `emp`(`empid`,`empname`,`sex`,`birthday`,`hire_date`,`salary`,`address`,`deptid`) VALUES
(1,'王八',0,'2000-10-31','2021-10-01 09:00:00',8000,'杭州江干',2),
(2,'李四',1,'1985-11-10','2006-12-12 18:10:10',9000,'李家村',1),
(3,'王五',0,'1991-10-02','2009-12-02 00:00:00',1500,'王家村',2),
(5,'AA',1,'2021-10-04','2021-11-11 00:00:00',15000,'杭州',2),
(7,'CC',1,'2019-05-12','2021-11-07 00:00:00',6000,'杭州',2),
(8,'DD',1,'2021-11-05','2021-11-05 00:00:00',6000,'杭州',1),
(9,'九妹儿',0,'2021-11-11','2005-12-06 00:00:00',11111,'远古时期',NULL),
(10,'萧炎',1,'1999-12-12','2021-02-03 00:00:00',5000,'牛田村',1),
(11,'萧媚',1,'1985-11-11','2006-12-12 00:00:00',9000,'李家村',1),
(12,'火灵儿',0,'1991-10-02','2009-12-02 00:00:00',1500,'王家村',2),
(13,'林动',1,'2021-11-05','2021-11-05 00:00:00',6000,'杭州',1),
(14,'凌青竹',1,'2021-11-05','2021-11-05 00:00:00',6000,'杭州',1),
(15,'纪宁',1,'2021-11-05','2021-11-05 00:00:00',6000,'杭州',2),
(16,'北冥',1,'2021-11-05','2021-11-05 00:00:00',6000,'杭州',1),
(17,'顾清风',0,'2021-11-11','2005-12-06 00:00:00',11111,'远古时期',NULL),
(18,'余生',0,'2021-11-11','2005-12-06 00:00:00',11111,'远古时期',2),
(19,'花解语',0,'2021-11-11','2005-12-06 00:00:00',11111,'远古时期',1),
(100,'张三',1,'1999-12-12','2021-02-03 00:00:00',5000,'牛田村',1);
2.2 一对多
Mapper接口和Mapper映射文件
- Mapper接口
public interface DeptMapper {
Dept findByIdAndEmps(Integer id);
}
- Mapper映射文件
<resultMap id="DeptAndEmpsResultMap" type="com.gl.ssm.pojo.Dept" extends="BaseResultMap">
<!-- 映射一对多【集合】 -->
<!--
collection: 映射一对多【集合】
property: 集合名
ofType : 集合中的元素类型
-->
<collection property="emp" ofType="com.gl.ssm.pojo.Emp">
<!-- 映射单个员工 -->
<id column="empid" property="id"/>
<result column="empname" property="empName"/>
<result column="sex" property="sex"/>
<result column="birthday" property="birthday"/>
<result column="hire_date" property="hireDate"/>
<result column="salary" property="salary"/>
<result column="address" property="address"/>
<result column="deptid" property="deptId"/>
</collection>
</resultMap>
<resultMap id="BaseResultMap" type="com.qf.java2107.pojo.Department">
<id column="id" property="id"/>
<result column="dept_name" property="deptName"/>
</resultMap>
<!-- Department findByIdAndEmps(Integer id); -->
<select id="findByIdAndEmps" parameterType="int" resultMap="DeptAndEmpsResultMap">
SELECT
d.id,
d.dept_name,
e.id emp_id,
e.emp_name,
e.gender,
e.birthday,
e.hire_date,
e.salary,
e.address,
e.dept_id
FROM
t_department d, t_employee e
WHERE d.id = e.dept_id
AND d.id = #{id}
</select>
2.3 一对一
- 根据ID查询员工信息且关联部门信息
Mapper接口和Mapper映射文件
- Mapper接口
public interface IEmployeeMapper {
Employee findByIdAndDept(Integer id);
}
- Mapper映射文件
<?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.gl.ss.mapper.EmpMapper">
<resultMap id="BaseResultMap" type="com.gl.ssm.pojo.Emp">
<id column="id" property="id"/>
<result column="empname" property="empName"/>
<result column="sex" property="sexr"/>
<result column="birthday" property="birthday"/>
<result column="hire_date" property="hireDate"/>
<result column="salary" property="salary"/>
<result column="address" property="address"/>
<result column="deptid" property="deptId"/>
</resultMap>
<resultMap id="EmpAndDeptResultMap" type="com.gl.ssm.pojo.Emp" extends="BaseResultMap">
<!--
association : 映射实体
property : 实体属性名
javaType : 实体属性全类名
-->
<association property="department" javaType="com.gl.ssm.pojo.Dept">
<id column="deptid" property="deptid"/>
<result column="deptname" property="deptname"/>
</association>
</resultMap>
<!-- Employee findByIdAndDept(Integer id); -->
<select id="findByIdAndDept" parameterType="int" resultMap="EmpAndDeptResultMap">
SELECT
e.id empid,
e.empname,
e.sex,
e.birthday,
e.hire_date,
e.salary,
e.address,
e.deptid,
d.id deptid,
d.deptname
FROM
t_emp e JOIN t_dept d
ON e.deptid = d.deptid
WHERE e.id = #{id}
</select>
</mapper>
十、分步查询、延迟加载
1、分步查询
- 是连表查询的另一种方式
- 把连表查询的SQL进行拆分出多个单表查询的SQL
2、一对多
- 查询部门信息关联查询员工信息
2.1 DeparmentMapper映射文件
<!-- =================分步查询====================== -->
<resultMap id="DeptAndEmpStepQueryResultMap" type="com.gl.ssm.pojo.Dept">
<id column="deptid" property="deptid"/>
<result column="deptname" property="deptname"/>
<!-- 映射集合 -->
<collection property="Emp" ofType="com.gl.ssm.pojo.Emp"
select="com.gl.ssm.mapper.EmpMapper.findByDeptId" column="empid">
</collection>
</resultMap>
<!--Department findByIdUseStepQuery(Integer empid);-->
<select id="findByIdUseStepQuery" parameterType="int" resultMap="DeptAndEmpStepQueryResultMap">
SELECT deptid, deptname FROM t_dept WHERE deptid = #{deptid}
</select>
2.2 EmployeeMapper映射文件
<!--==================分步查询相关======================== -->
<!--List<Employee> findByDeptId(Integer deptid);-->
<select id="findByDeptId" parameterType="int" resultMap="BaseResultMap">
SELECT * FROM t_emp WHERE deptid = #{deptid}
</select>
3、延迟加载【面试题】
也叫懒加载,也叫按需加载
当需要使用到关联的数据时,才去执行查询操作
实现原理:Cglib动态代理【基于继承】
延迟加载只会出现在分步查询中。一般延迟加载的数据都是大数据【如集合】
- 即时加载:先执行完所有的SQL,再打印数据
- 延迟加载,先执行需要获取数据的SQL,再打印数据。后面如果还需要获取数据,再执行SQL,再打印数据
十一、动态SQL
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
1、if
- 条件判断
- 成立,就拼接SQL
2、where(起始特殊形式的trim应用)
- 功能相当于数据库的where关键字
<!-- List<Employee> findWithIf(Employee employee); -->
<select id="findWithIf" parameterType="com.gl.ssm.pojo.Emp" resultType="com.qf.java2107.pojo.Employee">
SELECT * FROM t_emp
<!--WHERE 1=1-->
<!-- where 会忽略条件成立的最前面的多余的and或者or -->
<where>
<if test="empname != null and empname.trim() != ''">
AND empname LIKE #{empname}
</if>
<if test="salary != null and salary > 0">
AND salary = #{salary}
</if>
<if test="sex == 0 || sex == 1">
AND sex = #{sex}
</if>
<if test="deptid != null">
AND deptid = #{deptid}
</if>
<!-- 除SQL之外的地方都是JavaBean的属性名 -->
</where>
</select>
3、trim【理解】
- 可以自定义的拼接或去掉SQL片段前后缀
<!-- List<Emp> findWithTrim(Emp emp); -->
<select id="findWithTrim" parameterType="com.gl.ssm.pojo.Emp" resultType="com.gl.ssm.pojo.Emp">
SELECT * FROM t_emp
<!--
prefix : 要加的前缀
prefixOverrides : 要去掉的前缀
suffix : 要加的后缀
suffixOverrides : 要去掉的后缀
-->
<!--<trim prefix="where" prefixOverrides="and | or" suffix="" suffixOverrides="">
<if test="empname != null and empname.trim() != ''">
AND empname LIKE #{empname}
</if>
<if test="salary != null and salary > 0">
AND salary = #{salary}
</if>
<if test="sex == 0 || sex == 1">
AND sex = #{sex}
</if>
<if test="deptid != null">
AND deptid = #{deptid}
</if>
</trim>-->
<trim prefix="where" prefixOverrides="" suffix="" suffixOverrides="AND | OR">
<if test="empname != null and empname.trim() != ''">
empname LIKE #{empname} AND
</if>
<if test="salary != null and salary > 0">
salary = #{salary} AND
</if>
<if test="sex == 0 || sex == 1">
sex = #{sex} AND
</if>
<if test="deptid != null">
deptid = #{deptid} AND
</if>
</trim>
</select>
4、forEach
<!-- List<Emp> findByIds(List<Integer> ids); -->
<select id="findByIds" parameterType="list" resultType="com.gl.ssm.pojo.Emp">
<!--SELECT * FROM t_emp WHERE id IN (1,25,3,62)-->
SELECT * FROM t_emp
<where>
<if test="list != null and list.size() > 0">
<!--id IN (1,25,3,62)-->
<!--
collection : 要遍历的集合,可以使用别名,但是如果使用了@Param指定入参key,那么就指定这个key
item : 正在迭代的元素名,自己起名
separator : 元素之间的分隔符
open : 要遍历的元素开始之前的SQL片段
close : 要遍历的元素结束之后的SQL片段
-->
<foreach collection="list" item="id" separator="," open="id IN (" close=")">
#{id}
</foreach>
</if>
</where>
</select>
5、choose…when…otherwise【了解】
<!-- List<Emp> findWithChoose(Emp emp); -->
<select id="findWithChoose" parameterType="com.gl.ssm.pojo.Emp" resultType="com.gl.ssm.pojo.Emp">
SELECT * FROM t_emp
<where>
<choose>
<when test="empname != null and empname.trim() != ''">
AND empname LIKE #{empname}
</when>
<when test="salary != null and salary > 0">
AND salary = #{salary}
</when>
<when test="sex == 0 or sex == 1">
AND sex = #{sex}
</when>
<otherwise>
deptid = 1
</otherwise>
</choose>
</where>
</select>
6、sql…include
<!-- List<Emp> findByIds(List<Integer> ids); -->
<select id="findByIds" parameterType="list" resultType="com.gl.ssm.pojo.Emp">
<include refid="BaseSelect"></include>
<where>
<if test="list != null and list.size() > 0">
<foreach collection="list" item="id" separator="," open="id IN (" close=")">
#{id}
</foreach>
</if>
</where>
</select>
<sql id="BaseSelect">
SELECT
<include refid="BaseColumn"></include>
FROM t_emp
</sql>
<sql id="BaseColumn">
empid,
empname,
sex,
birthday,
hire_date,
salary,
address,
deptid
</sql>
7、set
- 仅用于更新,跟if配套使用
<!-- int updateWithSet(Emp emp); -->
<update id="updateWithSet" parameterType="Emp">
update t_emp
<set>
<if test="empname != null and empname.trim() != ''">
empname = #{empname},
</if>
<if test="salary != null and salary > 0">
salary = #{salary},
</if>
<if test="sex == 0 or sex == 1">
sex = #{sexr}
</if>
</set>
where id = #{id}
</update>
十二、缓存机制【理解】
- 作用
- 提高查询效率
- 减轻数据库访问压力
1、分类
- 一级缓存
- 二级缓存
2、区别
4、一级缓存
-
一级缓存默认开启,我们无法关闭他
-
在执行两次相同的查询时,第一次会向数据库发送SQL,并且写入一份到一级缓存中,那么后面的查询操作就直接从缓存中获取数据。不会向数据库发送SQL
-
一级缓存失效的情况
- 不是同一个SqlSession
- 两次相同的查询中间执行增删改
- 两次相同的查询中间手动清空缓存
- 两次相同的查询中间手动提交事务
@Test
public void firstLevelCacheTest() throws Exception {
EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
Emp emp1 = empMapper.findById(10);
System.out.println(emp1);
//1.两个SqlSession
//sqlSession = factory.openSession();
//empMapper = sqlSession.getMapper(EmpMapper.class);
//2.执行增删改
//empMapper.deleteById(111111);
//3.手动清空缓存
//sqlSession.clearCache();
//4.手动提交事务
sqlSession.commit();
Emp emp2 = empMapper.findById(10);
System.out.println(emp2);
System.out.println(emp1 == emp2);
}
5、二级缓存
- 二级缓存默认开启,我们可以通过配置文件对其关闭
- 二级缓存
- 必须在SqlSession关闭之后,数据才会被写入到二级缓存中。
- 存储介质是磁盘,所以写入的字节数据,那么要被写入的对象所在的类必须实现
Serializable
接口
- 实现步骤
- 全局配置文件开启
- 在要使用二级缓存的namespace中配置一个
<cache/>
标签 - 关闭SqlSession
/**
* 二级缓存
**/
@Test
public void secondLevelCacheTest() throws Exception {
EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
Emp emp1 = empMapper.findById(10);
System.out.println(emp1);
sqlSession.close();
sqlSession = factory.openSession();
empMapper = sqlSession.getMapper(EmpMapper.class);
Emp emp2 = empMapper.findById(10);
System.out.println(emp2);
System.out.println(emp1 == emp2);
}
以后可以使用redis数据库实现缓存,分布式锁
十三、#{}和${}的区别
-
#{}
- 在填充参数时,是通过
?
占位符的方式,能够避免SQL注入 - 只是获取跟数据库表列相关的值
- 在填充参数时,是通过
-
${}
-
在填充参数时,使用的是直接进行字符串拼接,会有SQL注入的风险
-
使用其可以操作非数据库表列的取值
-
优先选择#{}取值,如果不行,则使用${}
十四、注解开发【会用】
- 注解开发跟配置文件开发,选择一种
import com.gl.ssm.pojo.Dept;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface DeptMapper {
@Insert("insert into t_dept (deptname) values (#{deptname})")
@SelectKey(statement = "select last_insert_id()",
keyProperty = "id",
keyColumn = "deptid",
before = false,
resultType = int.class)
int insert(Department dept);
/**
* @Results 等同于配置文件的resultMap标签
* id 等同于配置文件的resultMap标签中id属性
*
* @Result : 映射单个属性,用boolean来区分是否是主键映射
*/
@Results(
id = "BaseResultMap",
value = {
@Result(id = true, column = "id", property = "id"),
@Result(id = false, column = "deptname", property = "deptname")
}
)
@Select("select id, deptname from t_dept")
List<Department>selectAll();
@ResultMap("BaseResultMap") //引用其他已经定义好的ResultMap
@Select("select id, dept_name from t_dept where id = #{id}")
Department findById(Integer id);
}
- 分步查询【IEmployeeAnnoMapper.class】
package com.gl.ssm.mapper;
import com.qf.java2107.pojo.Emp;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.mapping.FetchType;
public interface EmpMapper {
/**
* 根据ID查询员工关联部门
* @param id
* @return
*/
@Results({
@Result(id = true, column = "id", property = "id"),
@Result(id = false, column = "empname", property = "empname"),
@Result(id = false, column = "sex", property = "sex"),
@Result(id = false, column = "birthday", property = "birthday"),
@Result(id = false, column = "hire_date", property = "hireDate"),
@Result(id = false, column = "salary", property = "salary"),
@Result(id = false, column = "address", property = "address"),
@Result(id = false, column = "deptid", property = "deptid"),
@Result(id = false, property = "dept", column = "deptid",
//one 一对一映射
one = @One(select = "com.gl.ssm.mapper.DeptMapper.findById",
fetchType = FetchType.LAZY))
})
@Select("select * from t_emp where id = #{id}") //dept_id
Employee findByIdUseStep(Integer id);
}
十五、源码分析【听一遍】
1、查询
- 查询单个
- DefaultSqlSession的selectOne方法
- DefaultSqlSession的selectList方法,得到结果后,进行集合数量判断,如果是1,直接返回。>1,否则就报错。0返回null
- CachingExecutor的query方法
- BaseExecutor的query方法
- BaseExecutor的queryFromDatabase方法
- BaseExecutor的doQuery方法
- 通过SimpleExecutor中去调用JDBC的execute()
- BaseExecutor的doQuery方法
- BaseExecutor的queryFromDatabase方法
- BaseExecutor的query方法
- CachingExecutor的query方法
- DefaultSqlSession的selectList方法,得到结果后,进行集合数量判断,如果是1,直接返回。>1,否则就报错。0返回null
- DefaultSqlSession的selectOne方法
- 查询集合
- DefaultSqlSession的selectList方法,得到结果后,直接返回
- CachingExecutor的query方法
- BaseExecutor的query方法
- BaseExecutor的queryFromDatabase方法
- BaseExecutor的doQuery方法
- 通过SimpleExecutor中去调用JDBC的execute()
- BaseExecutor的doQuery方法
- BaseExecutor的queryFromDatabase方法
- BaseExecutor的query方法
- CachingExecutor的query方法
- DefaultSqlSession的selectList方法,得到结果后,直接返回
2、增删改方法
- 修改
- DefaultSqlSession的update方法
- CachingExecutor的update方法:会清空缓存操作
- BaseExecutor的doUpdate方法
- 通过SimpleExecutor中去调用JDBC的execute()
- BaseExecutor的doUpdate方法
- CachingExecutor的update方法:会清空缓存操作
- DefaultSqlSession的update方法
- 增加、删除
- 先调用DefaultSqlSession的insert方法或者delete方法
- 接下来就会执行DefaultSqlSession的update方法
- 先调用DefaultSqlSession的insert方法或者delete方法
十六、面试题
1.Mybatis应用到的设计模式
- 构建者模式 SqlSessionFactoryBuilder().build()
- 工厂模式 sqlSessionFactory.openSession();
- 代理模式 getMapper
2.Mybatis的Mapper接口是否支持方法重载
- 不支持
- mapper接口的方法名就是mapper映射文件的statementId,是用来获取执行SQL的唯一标识
3.Mybatis的Mapper映射文件的标签
- 除之前讲的之外
- sql:抽取的SQL片段
- include:引用抽取的SQL片段
<!-- List<Employee> findByIds(List<Integer> ids); -->
<select id="findByIds" parameterType="list" resultType="com.gl.ssm.pojo.Emp">
<include refid="BaseSelect"></include>
<where>
<if test="list != null and list.size() > 0">
<foreach collection="list" item="id" separator="," open="id IN (" close=")">
#{id}
</foreach>
</if>
</where>
</select>
<sql id="BaseSelect">
SELECT
<include refid="BaseColumn"></include>
FROM t_emp
</sql>
<sql id="BaseColumn">
empid,
empname,
sex,
birthday,
hire_date,
salary,
address,
deptid
</sql>