文章目录
MyBatis学习前需了解的知识
在学习mybatis之前,先学习一下代理模式
1 代理模式
1.1 什么是代理模式?
- 代理模式为一个对象提供一个代理对象,以控制对这个对象的访问。即通过代理对象访问目标对象,这样做的好处是:可以在不修改目标对象代码的基础上,增强额外的功能操作,即扩展目标对象的功能
- 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
- 代理模式有不同的形式,主要有静态代理、动态代理和 Cglib代理三种形式
1.2 核心角色
-
抽象角色(接口):定义公共对外方法。
-
真实角色(周杰伦):实现抽象角色,定义真实角色所要实现的业务逻辑。
-
代理角色(代理人):实现抽象角色,是真实角色的代理,通过调用真实角色的方法来完成业务逻辑,并可以附加自己的操作。
1.3 静态代理模式
话不多说,直接上案例
-
新建项目proxy,目录结构如下
-
新建Star抽象角色,里面放了所有的功能
public interface Star { /** * 面谈 */ void confer(); /** * 签合同 */ void signContract(); /** * 订票 */ void bookTicket(); /** * 唱歌 */ void sing(); /** * 收钱 */ void collectMoney(); }
-
RealStar,真实角色,继承抽象角色,周杰伦所干的事
public class RealStar implements Star{ @Override public void confer() { } @Override public void signContract() { } @Override public void bookTicket() { } @Override public void sing() { System.out.println("周杰伦:快使用双截棍"); } @Override public void collectMoney() { } }
-
ProxyFactory,代理角色,经济人所干的事
package com.by.proxy.StaticProxy; /** * @author qc * @version 0.1 * @className ProxyStar * @date 2024/6/4 18:55 * @since jdk11 */ public class ProxyStar implements Star{ private Star star; public ProxyStar(Star star) { this.star = star; } @Override public void confer() { System.out.println("宋吉吉正在:confer"); } @Override public void signContract() { System.out.println("宋吉吉正在:signContract"); } @Override public void bookTicket() { System.out.println("宋吉吉正在:bookTicket"); } @Override public void sing() { //让周杰伦唱歌 this.star.sing(); } @Override public void collectMoney() { System.out.println("宋吉吉正在:collectMoney"); } }
-
测试,新建客户端Client,也就是用户
public class Client { public static void main(String[] args) { Star proxy = new ProxyStar(new RealStar()); proxy.confer(); proxy.signContract(); proxy.bookTicket(); proxy.sing(); proxy.collectMoney(); } }
结果如下
各司其职,每个角色都做了自己该做的事1
1.3.1 静态代理的缺点
- 代理类和真实角色类也就是周杰伦,都实现了抽象角色的方法,真实角色只需要唱歌即可,有大量代码重复。
- 代理对象只服务于一种类型的对象,假设一个系统中有100个Service,则需要创建100个代理对象。
1.4 动态代理模式
为了解决静态代理的缺点,就引入了动态代理模式,动态代理模式就是新建一个代理工厂,工厂里有很多代理类,调用获取代理类的方法来获取代理类,这样就解决的静态代理的缺点
案例
-
新建Star接口,只写一个sing方法
public interface Star { void sing(); }
-
新建realStar,实现该接口,此时这个实现类只用重写一个sing方法
public class RealStar implements Star { @Override public void sing() { System.out.println("周杰伦:快使用双截棍"); } }
-
新建一个代理工厂,新建getProxyObject方法,该方法目的就是获取代理对象
public class ProxyFactory { private Object realStar; public ProxyFactory(Object realStar) { this.realStar = realStar; } //获取代理对象 public Object getProxyObject(){ /* Proxy:作用创建代理对象 ClassLoader loader:类加载器 Class<?>[] interfaces:真实角色实现的接口,根据接口生成代理类 InvocationHandler h:增强的逻辑,即如何代理(宋吉吉要做的事) */ return Proxy.newProxyInstance(realStar.getClass().getClassLoader(), realStar.getClass().getInterfaces(), new InvocationHandler() { /** * @param proxy:代理类,一般不用 * @param method:要调用的方法 * @param args:调用方法时的参数 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("宋吉吉正在:confer"); System.out.println("宋吉吉正在:signContract"); System.out.println("宋吉吉正在:bookTicket"); Object invoke = method.invoke(realStar, args); System.out.println("宋吉吉正在:collectMoney"); return invoke; } }); } }
-
新建用户类,在里面调用方法
public class Client { public static void main(String[] args) { //new一个ProxyFactory工厂将RealStar传进去再调用getProxyObject方法获取到一个代理对象 Star proxyObject = (Star) new ProxyFactory(new RealStar()).getProxyObject(); //通过该代理对象可以直接调用sing方法,实际调用的是invoke方法 proxyObject.sing(); } }
2 MyBatis介绍
2.1 什么是框架
在介绍什么是mybatis之前首先了解一下框架,框架就是(Framework)是一个框子——指其约束性,也是一个架子——指其支撑性,即已经对基础的代码进行了封装并提供相应的API,开发者在使用框架是直接调用封装好的api可以省去很多代码编写,从而提高工作效率和开发速度。
2.2 传统jdbc的缺点
-
手动创建和释放链接
-
sql语句在代码中硬编码
-
对结果的解析
2.3 什么是mybatis
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis。iBatis一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。
MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、获取结果集等jdbc繁杂的过程代码。具有较高的SQL灵活性,支持高级映射(一对一,一对多),动态SQL,延迟加载和缓存等特性。
3 MyBatis入门案例
先创一个mybatis的数据库,然后在添加一个表,表里数据随便填填
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(32) NOT NULL COMMENT '用户名称',
`password` varchar(20) DEFAULT NULL,
`birthday` datetime DEFAULT NULL COMMENT '生日',
`sex` char(1) DEFAULT NULL COMMENT '性别',
`address` varchar(256) DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=49 DEFAULT CHARSET=utf8;
![image-20240606211221962](https://img-blog.csdnimg.cn/img_convert/599154063ab7ca66c7f60c4093643288.png)
项目目录如下:
![image-20240611144724357](https://img-blog.csdnimg.cn/img_convert/e17324614290273e29602a8c3fdefa48.png)
3.1 添加必要的依赖
pom.xml
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!--这里选择自己的mysql版本-->
<version>5.1.49</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--这个依赖可以选择性添加-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<!-- 如果不添加此节点src/main/java目录下的所有配置文件都会被漏掉。 -->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
3.2 添加日志配置文件
log4j.properties,主要是对日志的一些打印,能够在控制台看到
# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
3.3 映射类
pojo/User.java
@Data
@Accessors(chain = true)
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
}
3.4 UserMapper
提供查询方法
public interface UserMapper {
/**
* 查询所有用户信息
* @return 存放用户信息
*/
public List<User> findAll();
}
3.5 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">
<!--namespace:隔离sql,一般是接口名称的全类名-->
<mapper namespace="com.by.mapper.UserMapper">
<!--
id:和接口方法名保持一致
resultType:和接口返回类型保持一致
-->
<select id="findAll" resultType="com.by.pojo.User">
select * from user
</select>
</mapper>
3.6 mybatis-config.xml
存放所有配置信息,数据库连接信息,以及sql语句
<?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>
<!-- 和spring整合后 environments配置将废除-->
<environments default="dev">
<!-- dev环境 -->
<environment id="dev">
<!-- 配置事务的类型:type="JDBC | MANAGED" 两种方式
JDBC:表示使用JDBC中原生的事务管理方式
MANAGED:被管理,例如Spring
-->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置连接数据库的信息:用的是数据源(连接池) -->
<dataSource type="POOLED">
<!-- mysql5 -->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3307/mybatis?characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123"/>
</dataSource>
</environment>
</environments>
<!-- 告知 mybatis 映射配置的位置 -->
<mappers>
<mapper resource="com/by/mapper/UserMapper.xml"/>
</mappers>
</configuration>
- 注意mysql8的driver和url
<!-- mysql8 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&serverTimezone=Asia/Shanghai"/>
3.7 测试
test/java/com/by/test/MyBatisTest.java,单元测试
public class MyBatisTest {
@Test
public void test() throws Exception{
// 加载配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
// 创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//获得数据库会话实例
SqlSession sqlSession = sqlSessionFactory.openSession();
//得到代理类对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.findAll();
userList.forEach(System.out::println);
}
}
运行结果
4 MyBatis的运行原理
这里我总结了一张图,详细介绍了MyBatis的整体运行流程
MyBatis框架在操作数据库时,大体经过了以下几个步骤:
- 读取配置信息:通过Resources的getResourceAsStream(“mybatis-config.xml”)方法来加载配置文件,并返回了一个InputStream流对象,里面存放了sql语句和连接信息(包含url,username,password,dirver等)
- 构造会话工厂SqlSessionFactory:new一个SqlSessionFactoryBuilder类,该类有一个build方法,将获取的is流传过去,会返回一个SqlSessionFactory。build方法内部先通过工具类XMLConfigBuilder调用loadConfiguration(is)方法将流传进来,获得一个Configuration对象,所以Configuration对象里就存放了is流里的一些配置信息,然后再将Configuration对象传入工厂对象DefaultSqlSessionFactory中并返回,因为DefaultSqlSessionFactory是SqlSessionFactory的实现类,所以返回的是SqlSessionFactory对象。
- Configuration如何储存配置信息:Configuration(conn, Map<namespace+“.”+id, MappedStatement(sql, resultType)>),包含了conn连接和MappedStatement,MappedStatement又放了一些sql语句和结果类型也就是一些映射文件。
- 创建会话对象SqlSession:SqlSession是由SqlSessionFactory会话工厂创建的,因为会话工厂实际返回的是DefaultSqlSessionFactory实现类,而DefaultSqlSessionFactory中实现了会话工厂的openSession()方法,该方法返回了一个SqlSession会话对象。
- 获取代理对象:SqlSession对象内部有很多和数据库相关的方法,这里调用getMapper()这个方法,这个方法的参数传的是接口的.class对象,内部是返回了一个代理对象 return Proxy.newProxyInstance(…) 内部三个参数,第一个是类加载器,第二个是接口,第三个new ProxyFactory是一个代理工厂,通过代理工厂得到一个代理对象。
- ProxyFactory:proxyFactory调用代理对象的任何方法,都会在invoke中执行,根据method的getName和getDeclaringClass().getName()获得方法名和方法所在类的名字组合到一起得到Map的key,通过key获取value值,也就是获取到了一个MappedStatement,再将连接conn和MappedStatenment传入执行器Executor中。
- Executor:执行器,主要是发送sql语句,输出结果映射.
5 自定义Mybatis
了解MyBatis的核心对象即可
5.1 MyBatis的核心对象
-
Resources
加载配置文件,有一种是使用类加载进行加载,我们通过这个类的类加载器进行资源的加载。
-
SqlSessionFactoryBuilder
构建SqlSessionFactory工厂对象需要的对象。采用了构建者模式,屏蔽了对象构建的细节。
-
SqlSessionFactory
创建SqlSession对象所用。使用工厂模式创建,目的就是解耦合。
-
SqlSession
创建代理对象,使用了代理模式。
-
Executor
操作数据库
-
MappedStatement
存储SQL语句、参数、输出结果类型
6 MyBatis的CRUD
新建项目,完善项目
![image-20240611102439146](https://img-blog.csdnimg.cn/img_convert/b23c2b42799faa7265a53c97517651ce.png)
6.1 查询
6.1.1 单个参数绑定
MyBatisTest
@Test
public void findUserByIdTest() throws Exception{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.findUserById(46);
System.out.println(user);
}
UserMapper
public interface UserMapper {
/**
* 根据id查询用户信息
*
* @param id 用户id
* @return 查询到的用户信息
*/
User findUserById(Integer id);
}
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">
<!--namespace:隔离sql,一般是接口名称的全类名-->
<mapper namespace="com.by.mapper.UserMapper">
<!--
id:和接口方法名保持一致
resultType:和接口返回类型保持一致
-->
<!-- 单个参数绑定-->
<select id="findUserById" parameterType="java.lang.Integer" resultType="com.by.pojo.User">
SELECT * FROM user WHERE id=#{id}
</select>
</mapper>
6.1.2 序号参数绑定
MyBatisTest
@Test
public void findUserByIdAndNameTest() throws Exception{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.findUserByIdAndName(46,"殷梨亭");
System.out.println(user);
}
}
UserMapper
public interface UserMapper {
/**
* 根据id和姓名查询用户
* @param id id
* @param username 用户名
* @return 查询到的用户信息
*/
User findUserByIdAndName(int id, String username);
}
UserMapper.xml
<mapper namespace="com.by.mapper.UserMapper">
<!-- 序号参数绑定有两种方式,取其中一种即可,不太推荐使用-->
<!-- 方式1-->
<!-- arg从零开始往上递增-->
<select id="findUserByIdAndName" resultType="com.by.pojo.User">
SELECT * FROM user WHERE id=#{arg0} AND username=#{arg1}
</select>
<mapper namespace="com.by.mapper.UserMapper">
<!-- 方式2-->
<!-- param从1开始往上递增-->
<select id="findUserByIdAndName" resultType="com.by.pojo.User">
SELECT * FROM user WHERE id = #{param1} AND username = #{param2} <!--param1 param2 param3 ...-->
</select>
</mapper>
6.1.3 注解参数绑定
适用于少量参数,SQL语句中参数与注解中的参数保持一致
MyBatisTest
//注解参数绑定
@Test
public void findUserByIdAndNameTest2() throws Exception{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.findUserByIdAndName2(46,"殷梨亭");
System.out.println(user);
}
UserMapper
/**
* 根据id和姓名查询用户
* @param id id
* @param username 用户名
* @return 查询到的用户信息
*/
User findUserByIdAndName2(@Param("id")Integer id,@Param("username") String username);
UserMapper.xml
<!-- 注解参数绑定,SQL语句中参数与注解中的参数保持一致即可-->
<select id="findUserByIdAndName2" resultType="com.by.pojo.User">
SELECT * FROM user WHERE id = #{id} AND username = #{username}
</select>
6.1.4 对象参数绑定
适用于多个参数,推荐使用
MyBatisTest
//对象参数绑定
@Test
public void findUserByUserInfoTest() throws Exception{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User userInfo = new User().setId(46).setUsername("殷梨亭");
User user = mapper.findUserByUserInfo(userInfo);
System.out.println(user);
}
UserMapper
/**
* 根据对象信息查询对象
* @param userInfo 对象信息
* @return 查询到的对象信息
*/
User findUserByUserInfo(User userInfo);
UserMapper.xml
<!-- 对象参数绑定-->
<select id="findUserByUserInfo" parameterType="com.by.pojo.User" resultType="com.by.pojo.User">
SELECT * FROM user WHERE id = #{id} AND username = #{username}
</select>
6.1.5 Map参数绑定
不推荐使用,就是将对象的属性名和属性值作为键值对放入map中传到方法里
MyBatisTest
//Map参数绑定
@Test
public void findUserByMapTest() throws Exception{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("id",46);
map.put("username","殷梨亭");
User user = mapper.findUserByMap(map);
System.out.println(user);
}
UserMapper
/**
* 利用map进行参数绑定,查询用户信息
* @param map 存放查询的用户信息
* @return 查询到的用户信息
*/
User findUserByMap(Map<String, Object> map);
UserMapper.xml
<!-- Map参数绑定-->
<select id="findUserByMap" parameterType="java.util.Map" resultType="com.by.pojo.User">
SELECT * FROM user WHERE id = #{id} AND username = #{username}
</select>
6.1.6 模糊查询
MyBatisTest
//模糊查询
@Test
public void findUserByNameTest() throws Exception{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//查询姓张的用户
List<User> userList = mapper.findUserByName("张");
userList.forEach(System.out::println);
}
UserMapper
/**
* 根据用户名模糊查询
* @param username 用户名
* @return 存储多个用户的集合
*/
List<User> findUserByName(String username);
UserMapper.xml
<!-- 模糊查询-->
<select id="findUserByName" parameterType="java.lang.String" resultType="com.by.pojo.User">
<!-- select * from user where username like concat('%',#{username},'%') -->
<!--SELECT * FROM user WHERE username like '%#{username}%' 错误!!!-->
select * from user where username like '%${value}%'<!--${}括号中只能是value-->
</select>
6.1.7 聚合函数查询
对组函数的使用
MyBatisTest
//聚合函数查询
@Test
public void countUserTest() throws Exception{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Integer count = mapper.countUser();
System.out.println("人数为:"+count);
}
UserMapper
/**
* 统计用户数
* @return 统计的用户数量
*/
Integer countUser();
UserMapper.xml
<!-- 聚合函数 -->
<select id="countUser" resultType="java.lang.Integer">
<!-- SELECT count(id) FROM user-->
SELECT count(1) FROM user
</select>
6.2 SQL注入
SQL注入(SQL Injection)是一种常见的网络攻击手段,它利用了应用程序对用户输入数据的不安全处理,让攻击者能够向数据库发送恶意的SQL代码。
在了解SQL注入问题之前,先了解一些#{}和${}的区别
6.2.1 #{}和${}的区别
单个简单类型参数 | 类型转换 | 底层 | sql注入 | |
---|---|---|---|---|
#{} | 无限制 | 转换 | preparedStatement | 防止 |
${} | 必须用value | 不转换 | Statement | 不防止 |
案例
MyBatisTest
//#{}和${}的区别
@Test
public void getUserByUserNameTest() throws Exception{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
String username = "张三丰";
User user = mapper.getUserByUserName(username);
System.out.println(user);
}
UserMapper
/**
* 根据名字找到该对象信息
* @param username 用户名
* @return 用户信息
*/
User getUserByUserName(String username);
UserMapper.xml
<!-- #{} 和 ${} 的使用-->
<select id="getUserByUserName" parameterType="java.lang.String" resultType="com.by.pojo.User">
<!-- SELECT * FROM user WHERE username= #{username}-->
<!-- #{} 已经在prepareStatement预编译过了,会发生类型转换,可以防止sql注入-->
<!-- SELECT * FROM user WHERE username= #{qwersad} 参数可以随便写-->
<!--SELECT * FROM user WHERE username = ${uesername}
这是错误的,因为没用经过编译而是直接通过Statement进行拼接,没有调用get方法
必须填value, 并且value需要用''包裹住,才能进行正常查询,不能防止sql注入问题,
-->
select * from user where username = '${value}'
</select>
6.2.1 解决SQL注入问题
用#{}
MyBatisTest
//SQL注入问题
@Test
public void loginTest() throws Exception{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
String username = "张三丰' #";
String password = "111";
User user1 = new User().setUsername(username).setPassword(password);
User user = mapper.login(user1);
System.out.println(user);
}
UserMapper
/**
* 登录方法
* @param user 用户信息
* @return 查询到的用户对象
*/
User login(User user);
UserMapper.xml
<!--sql注入问题-->
<select id="login" parameterType="com.by.pojo.User" resultType="com.by.pojo.User" >
<!--
采用'${}'的方法无法防止sql注入问题,因为底层是Statement只是进行字符串的拼接
如果用户在参数中传入' # 会使后续语句失效
username= '张三丰' # ' AND password = '111'中,
AND password = '111'这一串语句就会失效,密码就不会校验,随意登录
-->
<!--
SELECT * FROM user WHERE username='${username}' AND password = '${111}'
不可取,有sql注入风险
-->
<!--
当换成#{}的方法时,就不会出现这种问题,因为MyBatis会对参数进行预编译处理,生成安全的SQL查询。 即使用户名参数包含恶意字符,由于参数值被当作占位符处理并与SQL主体分开,攻击者无法通过修改参数 来操控SQL结构。因此,同样的恶意输入在使用#{}的情况下,SQL查询会安全地变为:
SELECT * FROM user WHERE username=? AND password = ?
-->
SELECT * FROM user WHERE username=#{username} AND password = #{password}
</select>
6.3 删除
MyBatisTest
//删除
@Test
public void deleteUserByIdTest() throws Exception{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.deleteUserById(48);
sqlSession.commit();
}
UserMapper
/**
* 根据id删除用户信息
* @param id id
*/
void deleteUserById(int id);
UserMapper.xml
<!-- 删除-->
<delete id="deleteUserById" parameterType="java.lang.Integer" >
DELETE FROM user WHERE id = #{id}
</delete>
6.4 修改
MyBatisTest
//修改
@Test
public void updateUserByIdTest() throws Exception{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Date date = new Date();
//这里将殷梨亭的信息改成郭靖的信息
User user = new User().setId(46).setUsername("郭靖").setPassword("111")
.setBirthday(date).setSex("男").setAddress("湖北省襄阳市");
mapper.updateUserById(user);
sqlSession.commit();
}
UserMapper
/**
* 根据id修改用户信息
* @param user 存放修改过的用户信息和要修改的id
*/
void updateUserById(User user);
UserMapper.xml
<!-- 修改-->
<update id="updateUserById" parameterType="com.by.pojo.User" >
update
user
set
username=#{username},password=#{password},
birthday=#{birthday},sex=#{sex},address=#{address}
where
id=#{id}
</update>
6.5 添加
MyBatisTest
//添加
@Test
public void insertUserTest() throws Exception{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Date date = new Date();
User user = new User().setUsername("张无忌").setPassword("111")
.setBirthday(date).setSex("男").setAddress("安徽省黄山市");
mapper.insertUser(user);
sqlSession.commit();
}
UserMapper
/**
* 添加用户信息
* @param user 要添加的用户信息
*/
void insertUser(User user);
UserMapper.xml
<!-- 添加-->
<insert id="insertUser" parameterType="com.by.pojo.User">
INSERT INTO
user(username,password,birthday,sex,address)
values
(#{username},#{password},#{birthday},#{sex},#{address});
</insert>
6.6 主键回填
MyBatisTest
//主键回填
@Test
public void insertUserTest1() throws Exception{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Date date = new Date();
User user = new User().setUsername("令狐冲").setPassword("111")
.setBirthday(date).setSex("男").setAddress("河南省洛阳市");
mapper.insertUser1(user);
System.out.println(user.getId());
sqlSession.commit();
}
UserMapper
/**
* 添加用户信息
* @param user 要添加的用户信息
*/
void insertUser1(User user);
UserMapper.xml
<!-- 主键回填-->
<!--
useGeneratedKeys=“true”:获取数据库生成的主键
keyProperty=“id”:主键对应实体类的属性
-->
<insert id="insertUser1" useGeneratedKeys="true" keyProperty="id" parameterType="com.by.pojo.User">
<!--
主键回填:新增之后,获取新增记录的id值
keyProperty="id":主键对应实体类的属性
order="AFTER":先执行插入语句,之后再执行查询语句
resultType="java.lang.Integer":主键的数据类型
-->
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
/*查询出刚刚插入的记录自增长id*/
select last_insert_id();
</selectKey>
insert into user(username,password,birthday,sex,address)
values(#{username},#{password},#{birthday},#{sex},#{address})
</insert>
7 MyBatis的ORM映射
项目目录:
![image-20240611144919030](https://img-blog.csdnimg.cn/img_convert/7cf066a6b9cd8374b88c01e6f098cbcc.png)
7.1 思考
MyBatis只能自动维护库表”列名“与”属性名“相同时的对应关系,二者不同时无法自动ORM,如下:
我们可以在查询的时候对相应的字段起别名:
/**
* 查询所有用户信息
* @return 存放用户信息
*/
public List<Role> findAll();
<!--列的别名-->
<select id="findAll" resultType="com.by.pojo.Role">
select id,role_name as roleName,role_desc as roleDesc from role
</select>
那如果有很多字段要查询呢,难道每个字段都要起别名吗?
7.2 ORM结果映射
这个时候我们可以使用resultMap标签手动映射,解决属性与表字段不一致的情况
<!--结果映射-->
<!-- id:和select标签的id属性值保存一致
type:映射实体的全类名
-->
<resultMap id="findAll2ResultMap" type="com.by.pojo.Role">
<!--
描述主键字段的映射关系:
property:实体类的属性
column:数据表字段名称
-->
<id column="id" property="id" />
<result column="role_name" property="roleName" />
<result property="roleDesc" column="role_desc" />
</resultMap>
<select id="findAll2" resultMap="findAll2ResultMap">
select * from role
</select>
8 MyBatis的配置文件
项目结构:
![image-20240611150127376](https://img-blog.csdnimg.cn/img_convert/78baa3923c0504bfe18b0cefffd9ce90.png)
8.1 properties标签
将数据库配置信息放在.properties文件里,例如:db.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3307/mybatis?characterEncoding=UTF-8
jdbc.username=root
jdbc.password=123
存放在properties中如何加载?在mybaties-config.xml文件中用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>
<!--引入db.properties-->
<properties resource="db.properties"></properties>
<environments default="dev">
<!-- dev环境 -->
<environment id="dev">
<!-- 配置事务的类型:type="JDBC | MANAGED" 两种方式
JDBC:表示使用JDBC中原生的事务管理方式
MANAGED:被管理,例如Spring
-->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置连接数据库的信息:用的是数据源(连接池) -->
<dataSource type="POOLED">
<!-- mysql5 -->
<!--使用${}占位符获取配置信息-->
<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>
<mappers>
<package name="com.by.mapper"></package>
</mappers>
</configuration>
8.2 typeAliases标签
主要是对类起别名,在标签的resultType里直接写别名即可,不用写全类名了
8.2.1 自定义别名
mybaties-config.xml
<typeAliases>
<!--定义单个别名 type是全类名 alies是别名-->
<!-- <typeAlias type="com.by.pojo.Role" alias="Role"></typeAlias> -->
<!--批量定义别名 直接填整个包-->
<package name="com.by.pojo"/>
</typeAliases>
RoleMapper.xml
<!--使用别名Role-->
<select id="findAll" resultType="Role">
select id,role_name as roleName,role_desc as roleDesc from role
</select>
8.2.2 MyBaties默认支持的别名
查看mybatis源码可以看到 Mybatis 默认支持的别名:
统统小写、统统基本类型、集合统统写接口
8.3 Mappers标签
Mappers标签的作用是用来在核心配置文件里面引入映射文件,引入方式有如下三种:
-
使用mapper映射文件的路径
<mappers> <mapper resource="com/by/mapper/RoleMapper"/> </mappers>
-
使用mapper接口的路径
<mappers> <mapper class="com.by.mapper.RoleMapper"></mapper> </mappers>
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同
-
使用mapper接口的包名批量引入(推荐使用)
<mappers> <package name="com.by.mapper"></package> </mappers>
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同
9 MyBatis的关联查询
项目结构:
![image-20240611155338635](https://img-blog.csdnimg.cn/img_convert/2d2fd3a8cd656ef85afd64ed059a230f.png)
9.1 什么是关联查询
-
实体间的关系(拥有 has、属于 belong)
-
OneToOne:一对一关系(account ←→ user)
-
OneToMany:一对多关系(user ←→ account)
-
ManyToMany:多对多关系(user ←→ role)
-
-
什么是关联查询
当访问关系的一方时,如果需要查看与之关联的另一方数据,则必须使用表链接查询,将查询到的另一方数据,保存在本方的属性中
-
关联查询的语法
指定“一方”关系时(对象),使用
< association javaType="" >
指定“多方”关系时(集合),使用
< collection ofType="" >
9.2 一对一查询
查询账户信息,关联查询用户信息,一个账户对应一个用户
9.2.1 pojo
Account
package com.by.pojo;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class Account implements Serializable {
private Integer id;
private Integer uid;
private double money;
//加入User作为Account的属性
private User user;
}
9.2.2 mapper
AccountMapper
/**
* 查询所有账户信息
* @return 所有的账户信息
*/
List<Account> findAll();
AccountMapper.xml
<mapper namespace="com.by.mapper.AccountMapper">
<!--手动映射-->
<resultMap id="findAllResultMap" type="com.by.pojo.Account">
<id column="aid" property="id"/>
<result column="uid" property="uid"/>
<result column="money" property="money"/>
<association property="user" javaType="user">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="birthday" property="birthday"/>
<result column="address" property="address"/>
</association>
</resultMap>
<select id="findAll" resultMap="findAllResultMap">
SELECT
a.id aid, a.uid uid, a.money, u.*
FROM
account a
left join
user u
ON
a.uid = u.id;
</select>
</mapper>
9.2.3 test
MyBatisTest
// 一对一。一个账户一个用户
@Test
public void testOneToOne() {
AccountMapper accountMapper = sqlSession.getMapper(AccountMapper.class);
List<Account> list = accountMapper.findAll();
list.forEach(System.out::println);
}
9.2.4 测试结果
9.3 一对多
一个用户可以拥有多个账户,根据一个用户查询多个账户.
9.3.1 pojo
User
@Data
@Accessors(chain = true)
public class User {
private Integer id;
private String username;
private String password;
private java.time.LocalDateTime birthday;
private String sex;
private String address;
//Account作为User的一个属性
private List<Account> accounts;
}
9.3.2 mapper
UserMapper
/**
* 找到所有用户信息
* @return 查询到的用户信息
*/
List<User> findAll();
UserMapper.xml
<mapper namespace="com.by.mapper.UserMapper">
<!-- 结果映射 -->
<resultMap id="findAllResultMap" type="com.by.pojo.User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="birthday" property="birthday"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
<!--
collection 是用于建立一对多中集合属性的对应关系
ofType 用于指定集合元素的数据类型
-->
<collection property="accounts" ofType="com.by.pojo.Account">
<id column="aid" property="id"/>
<result column="uid" property="uid"/>
<result column="money" property="money"/>
</collection>
</resultMap>
<select id="findAll" resultMap="findAllResultMap">
SELECT u.id ,u.username,u.password,u.birthday,u.sex,u.address,a.id aid,a.uid,a.money FROM user u left join account a ON u.id = a.uid;
</select>
</mapper>
9.3.3 test
MyBatisTest
// 一对多,一个用户多个账户
@Test
public void testOneToMany() {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> list = userMapper.findAll();
list.forEach(System.out::println);
}
9.3.3 测试结果
![image-20240611163041065](https://img-blog.csdnimg.cn/img_convert/8ba8ba84e80542e17e9db3393482c1d6.png)
张三丰用户有两个账户id分别是1和3
9.4 多对多查询
一个用户可以有多个角色,一个角色可以有多个用户
9.4.1 pojo
Role
@Data
@Accessors(chain = true)
public class Role {
private Integer id;
private String roleName;
private String roleDesc;
private List<User> users;
}
9.4.2 mapper
RoleMapper
/**
* 查询所有角色信息关联用户表
* @return 存放查询到的用户信息
*/
List<Role> findAll();
RoleMapper.xml
<mapper namespace="com.by.mapper.RoleMapper">
<resultMap id="findAllResultMap" type="com.by.pojo.Role">
<id column="rid" property="id"/>
<result column="role_name" property="roleName"/>
<result column="role_desc" property="roleDesc"/>
<!--本质上和一对多是一样的 -->
<collection property="users" ofType="com.by.pojo.User">
<id column="id" property="id"></id>
<result column="username" property="username"></result>
<result column="address" property="address"></result>
<result column="sex" property="sex"></result>
<result column="birthday" property="birthday"></result>
</collection>
</resultMap>
<!--
这时候就要借助中间表user_role来关联角色表和用户表,但是查询不查中间表,只做连接用。
-->
<select id="findAll" resultMap="findAllResultMap">
SELECT
u.*,r.id rid,r.role_name,r.role_desc
FROM
user u
left join
user_role ur
ON
u.id = ur.uid
left join
role r
ON
r.id = ur.rid;
</select>
</mapper>
9.4.3 test
MyBatisTest
// 多对多,一个用户多个角色,一个角色多个用户
@Test
public void testManyToMany() {
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
List<Role> roles = roleMapper.findAll();
roles.forEach(System.out::println);
}
9.4.4 测试结果
未完待续…