Mybatis学习与使用

2024-09-07 13:52
文章标签 学习 使用 mybatis

本文主要是介绍Mybatis学习与使用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1 MyBatis

SSM: Spring SpringMVC MyBatis。

2 介绍

MyBatis本是apache基金会的一个开源项目ibatis ,2010年这个项目由apache迁移到了google code, 并且改名为Mybatis。2013年11月代码迁移到了github。Mybatis是一个基于Java的持久层框架。用于简化数据库访问和操作。它提供了一种将数据库查询、插入、更新和删除操作与Java对象之间的映射的方式。

ORM:Object Relationship Mapping。 对象关系映射(说白了, 就是可以把Java中的对象映射成关系(数据表中的一条数据))。 其实Mybatis就是一个可以帮助我们把 关系型数据库中的记录转化为 Java对象,把Java对象转化为关系型数据库中的记录的这么一个框架。


举例来说。之前我们写得查询executeQuery()方法,查询user表,拿回来一个ResultSet对象,我们需要去遍历,如果我们在Java中有这样一个类User与之一一对应,这时候需要我们手动将这个类的属性设置进去,但是有了Mybatis,只要配置好映射关系,就可以自动完成映射。(也即从表中的记录 到 Java对象 这个过程)。Mybatis被称为ORM框架,就是负责将表中的数据映射到类上,建立两者之间的关系。

Mybatis就是一个可以帮助我们在Java代码中更加高效的操作数据库的这么一个框架。

官网 : https://mybatis.org/mybatis-3/zh/index.html

传统的JDBC查询代码

public class QueryDemo {public static void main(String[] args) {// JDBC连接信息String url = "jdbc:mysql://localhost:3306/test_52th3"; // 替换为你的数据库连接信息String username = "root"; // 替换为你的数据库用户名String password = "123456"; // 替换为你的数据库密码// SQL查询语句String sql = "SELECT * FROM user";List<User> resultList = new ArrayList<>();try (Connection connection = DriverManager.getConnection(url, username, password);Statement statement = connection.createStatement();ResultSet resultSet = statement.executeQuery(sql)) {while (resultSet.next()) {int id = resultSet.getInt("id");String name = resultSet.getString("name");String email = resultSet.getString("email");// 创建User对象并打印查询结果User user = new User(id, name, email);resultList.add(user);}} catch (SQLException e) {e.printStackTrace();}System.out.println(resultList);}
}

3 入门案例

导包

<!--mybatis-->
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.9</version>
</dependency><!-- 数据库驱动包 -->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version><scope>runtime</scope>
</dependency>

配置1: 配置一个Mybatis的主配置文件,用来获取SqlSessionFactory。(mybatis-config.xml

SqlSessionFactory:每一个Mybatis应用都是以SqlSessionFactory的实例对象为核心的。使用Mybatis必须以SqlSessionFactory的实例为核心,再以SqlSessionFactory的实例生产SqlSession实例对象的。


SqlSession:这个其实表示和数据库之间的一个连接,里面封装了 Connection对象

<?xml version="1.0" encoding="UTF-8" ?><!-- 约束文件 -->
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd"><!-- 注意这个configuration配置包括内部配置: 不要更换配置顺序  -->
<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/db7?useSSL=false&amp;characterEncoding=utf8"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment>
</environments><mappers><!--<mapper resource="UserMapper.xml"/>--><!--<mapper resource="com/cskaoyan/mapper/UserMapper.xml"/>-->
</mappers></configuration>

配置2: 配置一个专门用来存放SQL语句的配置文件UserMapper.xml

在Mybatis中,这样的文件可以有多个。我们使用一个xml文件来存放一组SQL, 比如对学生的SQL,对订单的SQL
这些文件,都必须在Mybatis的主配置文件中,声明进来

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!-- namespace: 命名空间,整个项目中必须唯一,暂时可以任意取名(后面再进行标准化) -->
<mapper namespace="AccountMapper"><!-- 每个标签都需要一个唯一的id属性: 每一个标签的id不能重复(本Mapper文件中), 用来标识一条SQL  -->
<!-- 在这个Mapper文件中, 怎么唯一表示SQL语句?namespace.id (命名空间.标签的id ) 是这个SQL语句的坐标  --><!-- <insert> 插入标签 --><!-- <delete> 删除标签 --><!-- <update> 修改标签 --><!-- <select> 查询标签 --><!-- parameterType:参数的类型(可以省略,标准语法要指明 ) --><!-- resultType:返回的结果集的类型(不能省略) --><select id="selectAccountById"  parameterType="java.lang.Integer" resultType="com.cskaoyan.bean.Account">select * from account where id = #{id}</select><select id="selectAccountList" resultType="com.cskaoyan.bean.Account">select * from account</select></mapper>

使用

@Test
public void test1() throws IOException {// 1.读取配置文件InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");// 2.创建一个SqlSessionFactorySqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// 3.通过SqlSessionFactory获得一个SqlSessionSqlSession sqlSession = sqlSessionFactory.openSession();// 4.执行SQL语句:传入SQL语句的坐标// 这里传入的 statement 是一个坐标,用来标识这个SQL的坐标。 使用 namespace.id 来唯一标识这条SQL// 参数写得1,代表传进去一个参数User user = sqlSession.selectOne("UserMapper.selectUserById", 1);// sqlSession.selectList();System.out.println(user);List<User> userList = sqlSession.selectList("UserMapper.selectUserList");System.out.println(userList);// 5.关闭连接sqlSession.close();
}

4 动态代理(Dynamic Proxy)

4.1 动态代理

Mybatis的动态代理可以帮助我们去生成接口的代理对象。我们可以自己不实现接口。

不需要实现接口,那么就需要遵守Mybatis使用动态代理的一些规则。

必须要遵循的规则

  1. 接口的全限定名称 和 mapper.xml中的namespace的值保持一致。

  2. 接口中的方法和 xml文件中的 <select> <insert> <update> <delete> 标签 一一对应,并且方法名要和标签的id值保持一致

  3. 方法的返回值类型和标签中的resultType保持一致(注意:添加/删除/修改不需要返回值类型)

  4. 参数保持一致(暂时可以不写)

建议要遵守的规则:希望

  1. 文件的名字 StudentMapper.xml | StudentMapper.java 建议保持一致。
  2. StudentMapper.xml 和StudentMapper.java 在编译之后的位置应该要在同一个路径下。

在这里插入图片描述

如何使用动态代理呢?

@Test
public void test1() {// 获取SqlSessionSqlSession sqlSession = MybatisUtil.getSqlSession();// 获取代理对象UserMapper userMapper = sqlSession.getMapper(UserMapper.class);// 调用对应的接口,执行User user = userMapper.queryUserById(1);System.out.println(user);
}

4.2 增删改查示例

4.2.1 添加

在UserMapper.xml中添加

<insert id="insertUser">insert into user(id,name,email) values (#{id}, #{name}, #{email})
</insert>

在UserMapper.java接口中添加

int insertUser(User user);

在测试类中的使用

@Test
public void testInsert() {User user = new User(4, "zhangsan", "zhangsan@qq.com");SqlSession sqlSession = MybatisUtil.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);int affectedRows = mapper.insertUser(user);System.out.println(affectedRows);// 获取到的SqlSession,默认不会自动提交。需要手动提交,数据才会进去sqlSession.commit();//Connection connection = sqlSession.getConnection();//connection.commit();
}

4.2.2 删除

在UserMapper.xml中添加

<delete id="deleteUserById">delete from user where id = #{id};
</delete>

在UserMapper.java接口添加

int deleteUserById(Integer id);

在测试类中的使用

@Test
public void testDeleteById() {SqlSession sqlSession = MybatisUtil.getSqlSession(true);UserMapper userMapper = sqlSession.getMapper(UserMapper.class);int affectedRows = userMapper.deleteUserById(1);System.out.println(affectedRows);
}

4.2.3 修改

在UserMapper.xml中添加

<update id="updateUser">update user set name = #{name}, email= #{email} where id = #{id};
</update>

在UserMapper.java接口添加

int updateUser(User user);

在测试类中的使用

@Test
public void testDeleteById() {SqlSession sqlSession = MybatisUtil.getSqlSession(true);UserMapper userMapper = sqlSession.getMapper(UserMapper.class);int affectedRows = userMapper.deleteUserById(1);System.out.println(affectedRows);
}

4.2.3 查询

在UserMapper.xml中添加

<select id="queryUserById" resultType="com.cskaoyan.bean.User">select *from userwhere id = #{id}
</select>

在UserMapper.java接口添加

User queryUserById(Integer id);

在测试类中的使用

@Test
public void testQueryById() {// 获取SqlSessionSqlSession sqlSession = MybatisUtil.getSqlSession();// 获取代理对象UserMapper userMapper = sqlSession.getMapper(UserMapper.class);// 调用对应的接口,执行User user = userMapper.queryUserById(1);System.out.println(user);
}

4.2.4 小结

使用MyBatis,主要分几步

  1. 在 Mapper.java接口中,定义方法;
  2. 在Mapper.xml中,写一个与之对应的标签,然后在标签中写SQL;
  3. 使用。

4.3 事务

在使用Mybatis的时候, 自带事务,而且事务默认情况下是不会自动提交的

4.3.1 解决方案一

执行完SQL语句之后, 使用sqlSession提交事务

// 解决办法一:  执行完SQL语句之后, 使用sqlSession提交事务
sqlSession.commit();

4.3.2 解决方案二

执行完SQL语句之后, 使用sqlSession内部封装的Connection 提交事务

// 解决办法二:  执行完SQL语句之后, 使用sqlSession内部封装的Connection 提交事务
Connection conn  = sqlSession.getConnection();
conn.commit();

4.3.3 解决方案三

(自动提交) 在获得SqlSession的时候, 给sqlSessionFactory.openSession设置为真

// 解决办法三:(自动提交) 在获得SqlSession的时候, 给sqlSessionFactory.openSession设置为真
// 获取到的SqlSession,里面的connection不会自动提交
SqlSession session = sqlSessionFactory.openSession();
// 获取自动提交的SqlSession
SqlSession session = sqlSessionFactory.openSession(true);  

5 搭建开发环境(MyBatis)

第一步: 导包,在pom.xml文件中导入依赖

<dependencies>
<!--mybatis-->
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.9</version>
</dependency><!-- 数据库驱动包 -->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version><scope>runtime</scope>
</dependency><!-- 测试包 -->
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope>
</dependency>
</dependencies>

第二步: 配置MyBatis的主配置文件(mybatis-config.xml)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN""https://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/test_52th?useSSL=false&amp;characterEncoding=utf8"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment>
</environments><!-- 去查找的Mapper文件 -->
<mappers><mapper resource="com/cskaoyan/mapper/UserMapper.xml"/>
</mappers></configuration>

第三步: 创建一个Java接口Mapper接口文件 (注意路径)

第四步: 创建一个与Java接口文件对应的Mapper.xml配置文件

在第三、四步骤中:

  1. 注意路径保持, 最终经过编译和对应接口文件编译到同一包下 --》 可以让开发者知道接口和这个xml是一起的
  2. 文件的名字和接口的文件的名字保持一致
  3. xml中的namespace(命名空间)的值要和Java接口的全限定名称保持一致

第五步: 把mapper.xml配置文件引入到主配置文件中(mybatis-config.xml)

<!-- 去查找的Mapper文件 -->
<mappers>
<mapper resource="com/snow/www/mapper/AccountMapper.xml"/>
</mappers>

第六步: 加载主配置文件(mybatis-config.xml)

// 1. 读取配置文件
InputStream inputStream = null;
try {inputStream = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {e.printStackTrace();
}// 2. 获取SqlSessionFactory
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);

第七步: 在对应的Mapper文件和对应的Java接口中, 声明SQL和声明方法

注意:

  1. 方法名和对应xml的SQL的id保持一致
  2. 参数和返回值设置正确
  3. 注意SQL返回值类型parameterType(结果集的解析是Mybatis自动完成的,不用我们自己解析)
  4. 注意SQL语句书写正确
// 添加
public int insertAccount(Account account);
// 查找
public Account selectAccountById(Integer id);
<insert id="insertAccount" >insert into account set id=#{id}, name=#{name}, money=#{money}
</insert>
<select id="selectAccountById"  parameterType="java.lang.Integer" resultType="com.snow.www.bean.Account">select * from account where id = #{id}
</select>

第八步: 获取SqlSession和代理的Mapper对象

// 1. 获取SqlSession
sqlSession = sqlSessionFactory.openSession(true);// 2. 获取接口的代理对象
studentMapper = sqlSession.getMapper(AccountMapper.class);

第九步: 通过代理对象调用方法执行SQL语句

Account account = new Account();
account.setId(10);
account.setName("snow");
account.setMoney(200);
int rows  = accountMapper.insertAccount(account);

6 配置(MyBatis)

主要是介绍Mybatis的核心配置文件。

在这里插入图片描述

6.1 properties

properties表示可以外部配置的属性,并可以进行动态替换。(作为典型的是JDBC配置)

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/db47?useSSL=false&characterEncoding=utf8
username=root
password=123456
<configuration><!-- 引入外部配置文件 --><properties resource="com/cskaoyan/mapper/jdbc.properties"/>
</configuration>

在这里插入图片描述
可以使用外部的propertie配置文件。

6.2 settings

settings是MyBatis的行为配置(类似于idea和settings的关系)

eg: 日志配置

<configuration><settings><!-- 添加日志的配置--><setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
</configuration>

一个完整的settings配置:

<settings><setting name="cacheEnabled" value="true"/><setting name="lazyLoadingEnabled" value="true"/><setting name="aggressiveLazyLoading" value="true"/><setting name="multipleResultSetsEnabled" value="true"/><setting name="useColumnLabel" value="true"/><setting name="useGeneratedKeys" value="false"/><setting name="autoMappingBehavior" value="PARTIAL"/><setting name="autoMappingUnknownColumnBehavior" value="WARNING"/><setting name="defaultExecutorType" value="SIMPLE"/><setting name="defaultStatementTimeout" value="25"/><setting name="defaultFetchSize" value="100"/><setting name="safeRowBoundsEnabled" value="false"/><setting name="safeResultHandlerEnabled" value="true"/><setting name="mapUnderscoreToCamelCase" value="false"/><setting name="localCacheScope" value="SESSION"/><setting name="jdbcTypeForNull" value="OTHER"/><setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/><setting name="defaultScriptingLanguage" value="org.apache.ibatis.scripting.xmltags.XMLLanguageDriver"/><setting name="defaultEnumTypeHandler" value="org.apache.ibatis.type.EnumTypeHandler"/><setting name="callSettersOnNulls" value="false"/><setting name="returnInstanceForEmptyRow" value="false"/><setting name="logPrefix" value="exampleLogPreFix_"/><setting name="logImpl" value="SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING"/><setting name="proxyFactory" value="CGLIB | JAVASSIST"/><setting name="vfsImpl" value="org.mybatis.example.YourselfVfsImpl"/><setting name="useActualParamName" value="true"/><setting name="configurationFactory" value="org.mybatis.example.ConfigurationFactory"/>
</settings>

6.3 typeAliases

typeAlies类型别名。(也就是我们可以对类起别名,简化操作) (不建议使用)

<configuration><!-- 类型别名 --><typeAliases><!-- alias别名 type全限定名 --><typeAlias alias="account" type="com.snow.bean.Account"/><typeAlias alias="user" type="com.snow.bean.User"/></typeAliases>
</configuration>
<select id="selectAccountById"  resultType="account">select * from account where id = #{id}
</select>

在这里插入图片描述

注意: Mybatis对于一些基本类型和包装类型,以及集合类型,有内置的别名。

值得注意的是下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,而且为了应对原始类型的命名重复,采取了特殊的命名风格。
注意: 除了内置别名, 不要乱起别名

别名映射的类型
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator

注意:

  1. typeHandlers: MyBatis 对我们SQL参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。(而在我们使用的时候是无法感知这个问题的, 属于MyBatis的底层处理).

  2. ObjectFactory: MyBatis 使用一个对象工厂实例来完成实例化工作。 默认的对象工厂要么通过默认无参构造方法,要么通过有参数的构造方法实例化对象。如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现。(不要使用)

6.4 environments

environments: 可以配置成适应多种环境.比如开发环境、测试环境和生产环境等可能需要有不同的配置.

<!-- 环境的配置,其实就是去配置数据库连接-->
<environments default="development"><!-- 环境的id,是唯一的-->
<environment id="development"><!-- 事务管理器JDBC:   使用JDBC连接来管理事务MANAGED: 把事务的管理交给外部的容器 --><!-- 如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。--><transactionManager type="JDBC"/><!--POOLED: 使用Mybatis自带的连接池UNPOOLED:不使用连接池JNDI:使用外部的连接池  --><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><environment id="test"><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><environment id="prod"><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>

6.5 mappers

这个是映射器的配置,配置mapper.xml配置文件。

  • 配置方式一: 直接以对应mapper文件的相对路径(相对target/classess路径)
<configuration>    <mappers><mapper resource="com/snow/mapper/AccountMapper.xml"/><mapper resource="com/snow/mapper/User.xml"/><!-- mapper中,可以写resource。(就是相对target/classes的目录)可以写url,里面就是写得绝对路径. file:/// ${绝对路径}可以写class,写得是类名的全限定类名,但是要注意类和映射文件要在相同路径下。类名和映射文件名字相同。--><mapper url="file:///D:\ideaProjects\java52-course-materials\mapper\UserMapper.xml"/><mapper class="com.cskaoyan.mapper.UserMapper"/></mappers>
</configuration>
  • 配置方式二: 配置某个包下的所有的配置文件
<configuration>    <mappers><package name="com.cskaoyan.demo5.mapper"/></mappers>
</configuration>

7 输入映射

输入映射其实就是在说Mybatis是如何传值的。

  • 只有一个参数
  • 传递多个参数
  • 对象传值
  • 使用Map传值
  • 按位置传值

7.1 一个参数

建议使用@Param注解,注解怎么写,xml中就怎么写。

  1. #{任意值} 来取值: 不建议使用, 建议使用注解写法
//一个参数: 一个入参
// 根据id来查找这个账户
// 1.如果方法的入参没有加任何的注解,那xml中可以随意写 。   #{id1lsdfjasdlfdjkl}
// 但是不建议大家随意写。  建议叫什么,写什么
Account queryAccountById1(Integer id);
<select id="queryAccountById1" resultType="com.cskaoyan.demo8.bean.Account">select *from accountwhere id = #{id1lsdfjasdlfdjkl}
</select>
  1. 如果在方法中 的一个参数 加了@Param注解,那么 后面就只能通过 #{注解值} 来取值
// 参数加了 @Param()注解  -->   注解里面怎么写,xml里面就怎么写。
Account queryAccountById2(@Param("id1") Integer id);
<select id="queryAccountById2" resultType="com.cskaoyan.demo8.bean.Account">select *from accountwhere id = #{id1};
</select>

7.2 多个参数

建议使用注解写,注解中怎么写,xml就怎么写。

注意:

  • 直接写多个值, 用参数名简单匹配是不识别的
  • 如果参数名简单匹配是不识别, 又不想加注解, 也是有别的解决手段(按位传值: 不建议), 但是建议加注解(最标准的写法)
// 4.推荐增加@Param注解。
// @Param里面怎么写,xml中就怎么写。  @Param("id1") xml中写 #{id1}
// 根据id获取name来查找数据。
List<Account> queryAccountByIdOrName2(@Param("id") Integer id, @Param("name") String name);
<select id="queryAccountByIdOrName2" resultType="com.cskaoyan.demo8.bean.Account">select *from accountwhere id = #{id}or name = #{name};
</select>

7.3 对象传值

常用。建议使用方式一。

方式一: SQL使用的参数命名要和对象内部属性保持一致 (#{成员变量名}来取值)

// 5.使用对象传值。对象没有注解
// 最后,在xml中,使用 #{成员变量名} 来取值
// 对象前面没有任何的注解,在xml中使用的时候,只要使用  #{对象属性名}即可
// 比如 Account 中有 name 和money.  最终想用name   #{name}
int insertAccount(Account account);
<insert id="insertAccount">insert into account(name, money)values (#{name}, #{money});
</insert>

方式二: 对象有注解, 必须通过 #{注解值 . 成员变量名} 来取值

// 使用对象传值,对象有注解
// 在xml中,应该写  #{注解值.成员变量名}
// 比如 @Param("user") User user.   我要使用里面的name  和  password
// #{user.name}  #{user.password}
int insertAccount2(@Param("account") Account account);
<insert id="insertAccount2">insert into account(id, name, money)values (#{account.id}, #{account.name},#{account.money});
</insert>

7.4 使用Map传值

Map 有 key value

对象里面。 属性名和属性值。 User(name age address )

Map传值: 不建议使用。因为写起来非常舒服,维护起来非常抓狂

方式一: SQL使用的参数命名要和Map中存储数据的key保持一致 (#{key} 来取值)

// 使用Map传值,无注解
// 在xml中,应该写  #{key}
// 比如这里有一个Map。里面有  {"name":"zhangsan", "age": 18}
// 最终,在xml中要取name . #{name}
int insertAccount3(Map<String, String> map);
<insert id="insertAccount3">insert into account(name, money)values (#{name}, #{money});
</insert>

方式二: Map对象有注解, 必须通过 #{注解值 . key} 来取值

// 使用map传值, 加上注解之后。  --> 在xml中,应该怎样写?
int insertUser5(@Param("map") Map<String, String> map);
// 使用Map传值,有注解
// 在xml中,应该写 #{注解值.key}
// 比如这里有一个Map。里面有  {"name":"zhangsan", "age": 18}  @Param("map")
// 最终,在xml中要取name . #{map.name}
int insertAccount4(@Param("map") Map<String, String> map);

7.5 按位置传值

按位传值: 完全不建议(容易因为程序员的记忆和修改导致bug产生, 除非除了按位传值没办法了)

方式一: arg0、arg1、arg2…

// 按位置传值。就是直接拿第一个参数,第二个参数,第三个参数
// arg0  arg1 arg2
// param1  param2 param3
int insertAccount5(String name, Integer money);
<!-- 在xml中,我们可以使用 arg0  arg1 arg2 这些来代表第一个参数,第二个参数,第三个参数-->
<!-- 我们也可以使用param1 param2 来代表第一个参数,第二个参数-->
<insert id="insertAccount5">insert into account(name, money)values (#{arg0}, #{arg1});
</insert>

方式二: param1、param2、param3…


// 不建议大家使用。 
int insertAccount6(Integer money, String name);
<insert id="insertAccount6">insert into account(name, money)values (#{param2}, #{param1});
</insert>

在Mybatis的输入映射中,我们经常使用前面三种方式(传入一个参数、传入多个参数、传递对象),后面通过map传值和按照位置来传值 一般不使用,也不建议大家使用。

推荐大家使用的写法

  • 一个参数时,带注解。User queryUserById2(@Param("id") Integer id); 在xml中#{id}

  • 多个参数,带注解。List<User> queryUsrByNameOrEmail2(@Param("name") String name, @Param("email") String email);

  • 对象。int insertUser2(User user);使用的时候,直接使用#{属性名}来使用属性

使用Map传值,和按照位置传值,了解即可,不推荐大家使用。也不要使用。

7.6 #和$的区别

  • #{参数}使用: 预编译占位 (尽量使用 #{} ) PreparedStatement
int insertUser6(@Param("name") String name, @Param("email") String email);mapper.insertUser6("zhangsan", "aaaaa");
<insert id="insertUser6">
insert into user(name, email)
values (#{name}, #{email})
</insert>

在这里插入图片描述

  • ${参数}使用: 字符串拼接, Statement (存在SQL注入问题)
int insertUser7(@Param("name") String name, @Param("email") String email);mapper.insertUser7("zhangsan", "aaaaa");
<insert id="insertUser7">
insert into user(name, email)
values ('${name}', '${email}')
</insert>

在这里插入图片描述

  • 面试可能会问到: #{}和${}的区别

#{}是使用的 prapareStatement。首先写SQL,然后使用?来进行占位,最后再把参数设置进去,可以防止SQL注入。

${}是使用的Statement。它是使用的字符串拼接。

7.6.1 注意

  1. 在开发的时候,应该尽量使用 #{} 去接收传递过来的参数值
  2. 当我们传递给SQL语句表名或者是列名的时候,就必须得使用 ${} 来取值。

有一些情景可能会用到 ${},一般不多。

分表问题: 动态表名
比如我的用户量非常大,有2个亿,全部放在一个表,不合适。 可以存10个表。 如果身份证尾号是0 :user_0 ; 尾号是1: user_1…。现在用户想找 421302199207080611,拿到这个id之后,需要怎么做?
尾号是1,所以去user_1 ,我们的这个表名是一个动态的。

// userMapper.dynamicTableName("user2");
userMapper.dynamicTableName("user");
List<User> dynamicTableNameList(String user);
<select id="dynamicTableNameList" resultType="com.snow.www.bean.User">select * from ${user}
</select>

分列问题: 动态列名

//  List<User> list = userMapper.dynamicColumnName("id");
List<User> list = userMapper.dynamicColumnName("age");
List<User> dynamicColumnName(String age);
<select id="dynamicColumnName" resultType="com.snow.www.bean.User">select * from user order by ${column}
</select>

8 输出映射

输出映射是指Mybatis是如何把SQL语句执行结果映射为 Java对象的。

  • 一个参数
  • 多个参数
  • 单个对象
  • 多个对象
  • resultMap: 比较重要(很常用)

8.1 一条结果

一行一列的结果,返回一个参数。比如根据id找名字。

一行一列: 必须要有resultType(写简单参数的全限定类名或者是别名)

String name = userMapper.selectNameById(5);
String queryNameById1(Integer id);
String queryNameById2(Integer id);
String queryNameById3(Integer id);
<!-- 我们必须要写一个resultType。标识查询的结果的类型-->
<!-- 对于java.lang.String 。我们既可以写 全限定类名,也可以写string。(别名不区分大小写)-->
<!-- 方式一: 全限定类名-->
<select id="queryNameById1" resultType="java.lang.String">select namefrom userwhere id = #{id};
</select><!-- 方式二: 内置的别名-->
<select id="queryNameById2" resultType="string">select namefrom userwhere id = #{id};
</select><!-- 方式三: 别名不区分大小写-->
<select id="queryNameById3" resultType="String">select namefrom userwhere id = #{id};
</select>

8.2 多条结果

多行一列。比如我们返回的是一个班级的名称列表。比如返回的是账户的所有name。

指: 多个结果构成的数组/List/Set

可以使用 List、Set 数组来接数据。在接口中,使用具体的类型。
在xml中,resultType写单个元素的类型。 比如拿回来是班上人的id,这时候用Integer; 比如拿回来是学生的姓名,这时候用 String

List<String> list = accountMapper.queryAllNameList();
Set<String> set = accountMapper.queryAllNameSet();
String[] names = accountMapper.queryAllNameArray();
List<String> queryAllNameList();
Set<String> queryAllNameSet();
String[] queryAllNameArray();
<select id="queryAllNameSet" resultType="java.lang.String">select namefrom account;
</select>

8.3 单个对象

一行多列。

单个对象:

  1. Mybatis在去映射的时候,会把成员变量名查询结果的列名去一一映射,假如原始表中的列名和成员变量名不一致的话,我们可以通过取别名的方式来解决(也可以通过resultMap来解决)
  2. 在声明JavaBean的成员变量的时候,尽量的使用包装类型
Account queryAccountById(@Param("id") Integer id);
<!-- resultType写得是bean的全限定类名。或者是别名-->
<!-- 需要注意。返回的列,需要和bean的成员变量名一致,如果不一致,可以使用取别名的方式解决-->
<select id="queryAccountById" resultType="com.cskaoyan.demo9.bean.Account">
select *
from account
where id = #{id};
</select>
@Test
public void testQueryAccountById() {
// id name  money存进去。   createTime  updateTime 没有存进去
// 因为数据库中的列名和 成员变量名不一样。 所以没映射上。
Account account = accountMapper.queryAccountById(2);System.out.println(account);
}

8.4 多个对象

多行多列。

多个对象:

  1. 数组/List/Set
  2. resultType的值是单个元素的类型。
@Test
public void testQueryAllUserList(){List<User> users = mapper.queryAllUserList();System.out.println(users);
}
// 多行多列,
// 比如查询一个班级里面的所有学生,在接口中可以使用 List 数组 Set来接。
// 在mapper.xml的标签中,resultType写单个元素的类型即可
List<User> queryAllUserList();
<!-- resultType中是单个元素的类型-->
<select id="queryAllUserList" resultType="com.cskaoyan.demo4.bean.User">select * from user;
</select>

8.5 resultMap

resultMap: 是用来做参数映射的。把数据库中的字段,与实体类中的字段进行映射的。

如果数据库里的字段,和bean中的对象,名字不一致,有两种解决方法。

  • 使用别名

  • 使用resultMap,更灵活

@Data
@AllArgsConstructor
@NoArgsConstructor
public class StudentDO {private Integer studentId;private String studentName;private Integer studentAge;private String address;
}drop table if exists mybatis_student;
CREATE TABLE `mybatis_student`  (`id` int(11) PRIMARY KEY AUTO_INCREMENT,`name` varchar(255)  ,`age` int(11)  ,`address` varchar(255)
);insert into mybatis_student(id,name,age,address) values (1, 'zhangsan', 18, 'hubei'),(2, 'lisi', 19, 'hunan'),(3, 'wangwu', 21, 'hubei'),(4, 'zhaoliu', 22, 'beijing');
List<StudentDO> selectStudentUseAlias();
List<StudentDO> selectStudentUseResultMap();
<!-- 方式一: 别名 --> 
<!-- 使用别名的方式来解决, 数据库中的字段和bean中的名称不一致问题。
名称不一致会有什么问题: 会查得出来属性,但是数据封装不进去。
-->
<select id="queryStudentUseAlias1" resultType="com.cskaoyan.demo4.bean.StudentDO">select * from mybatis_student;
</select><!-- 取别名可以解决这个问题。-->
<select id="queryStudentUseAlias2" resultType="com.cskaoyan.demo4.bean.StudentDO">select id as studentId, name as studentName, age as studentAge, address from mybatis_student;
</select>
<!-- 方式二: resultMap --> 
<!--resultMap的id属性 和 对应的映射的SQL标签的 resultMap对应-->
<!--resultMap的type属性 指代最终的对象类型-->
<resultMap id="studentResultMap1" type="com.cskaoyan.demo7.bean.StudentDO"><!-- id主键映射, result: 普通列映射--><!-- 在result标签中。  column是数据库的列名, property是成员变量名(bean里面的变量名)--><id column="id" property="studentId"/><result column="name" property="studentName"/><result column="age" property="studentAge"/><result column="address" property="address"/>
</resultMap><!-- 标签需要写resultMap 后面是id值-->
<select id="selectStudentUseResultMap" resultMap="studentResultMap1">select id, name, age, address from mybatis_student;	
</select>

9 插件

9.1 Lombok

Lombok: 可以帮助代码在编译的时候生成对应的方法。

  • getter
  • setter
  • toString
  • hashCode
  • equals

第一步: 安装插件

在这里插入图片描述

第二步: 导包

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>

打开一个 Annotation Processors --> Enable annotation processing

第三步: 使用

//@Getter@Setter@ToString@EqualsAndHashCode
//@NoArgsConstructor
//@AllArgsConstructor// @Data  -->  getter、setter、toString、equals、hashCode@Data
@AllArgsConstructor
@NoArgsConstructor
public class StudentDO {private Integer studentId;private String studentName;private Integer studentAge;private String address;
}

Lombok的优缺点

  1. 优势
    在去修改(增删改)成员变量的时候,不用我们自己再去生成getter、setter等,效率比较高
  2. 缺点
    Lombok在项目中,只要有一个人用了,那么其他的组员也都要使用

9.2 MybatisCodeHelperPro

MybatisCodeHelperPro: 这个是Mybatis的一个插件(提高开发Mybatis应用程序的效率)。

  • 帮助我们在mapper和mapper.xml 之前来回跳转
  • 可以帮助我们生成mapper.xml文件中的标签
  • 可以做一定的代码提示

步骤一: 插件安装
在这里插入图片描述

10 动态SQL

动态SQL是Mybatis给我们提供的又一个强大的功能。可以帮助我们根据传入的条件,动态的去改变SQL语句。

10.1 where

where这个标签可以帮助我们在最终执行的SQL中自动生成where关键字

  1. 可以自动拼接where关键字 (一般和if配合使用)

  2. 去除直接跟着的and或者是or关键字

  3. 如果where标签中没有条件满足的时候(如果SQL片段需要拼接),那么where标签不会给我们拼接where关键字

  4. 注意:一般if(工作用到更多一些),choose when otherwise(也会用,用到相对if少一些),都要注意, 尽量写在where标签内部,因为如果根据条件做处理的时候,没有任何一个条件满足,如果又使用的where标签(而不是写死的where关键字),那么最终执行的sql上不会生成where(避免出错)

<select id="queryAllPhone" resultType="com.cskaoyan.demo5.bean.Phone">select * from phone<where>and id = 1</where>;
</select>
<!-- if标签,只有条件满足,才会拼接进SQL-->
<select id="queryPhoneByBrandOrDisk" resultType="com.cskaoyan.demo5.bean.Phone">select * from phone<where><!-- 在if的test中,可以直接使用 输入映射中的参数。比如 brand disk--><!-- 输入映射可以写什么,在test中就可以写什么。比如有注解  @Param("id") 则可以直接写id。 不用外面的#{}--><if test="brand != null">brand = #{brand}</if><!-- 在if中,在test中可以写 ognl。 gt lt gte lte--><if test="disk != null">and disk = #{disk}</if></where>
</select><!-- 如果传入的brand是null,disk是null,则where这个关键字不会被拼接上去-->
select * from phone;

10.2 if

if标签可以帮助我们去做判断是否满足某个条件。如果符合条件,则拼接标签里面的内容;否则,不拼接。在if的条件中,输入映射中可以写什么,if中就可以写什么。

// 什么叫做输入映射怎么写, if的test中就怎么写
Phone queryPhone(@Param("id") Integer id);   ---> 在xml中。  #{id}  test="id"
Phone queryPhone(@Param("id") Integer id, @Param("name")String name);   ---> 在xml中。  #{id}   test="id"
Phone queryPhone(Phone phone);   ---> 在xml中。  #{成员变量名}   test="成员变量名" 
转义字符OGNL表达式
>&gt;gt
<&lt;lt
>=&gt;=gte
<=&lt;=lte
!=
==
and
or
  • <if test="">引号中,我们可以不使用转义字符,可以使用OGNL表达式的写法
  • 在if中,可以使用 > >= , 但是 < <=这种符号不能使用。所以不推荐大家使用这个

代码示例

<select id="queryPhoneByMemory" resultType="com.cskaoyan.demo5.bean.Phone">
select * from phone
<where><!-- memory 小于 8 。就拼接这个if--><if test="memory &lt; 8">memory = 8</if><!-- memory 大于 8,就拼接这个if--><if test="memory gt 8">and memory = 12</if></where>;
</select>

10.3 choose when otherwise

choose when otherwise就相当于Java中的 if …else if… else…

和where一起使用

//  如果传入的id大于5,就按传入的id查询; 否则根据传入的memory 是否等于8,等于8,就按照memory查询;
//  否则根据disk来进行匹配
List<Phone> queryByChooseWhenOtherwise(@Param("id") Integer id, @Param("memory") Integer memory, @Param("disk") Integer disk);
<select id="queryByChooseWhenOtherwise" resultType="com.cskaoyan.demo5.bean.Phone">
<!--        //  如果传入的id大于5,就按传入的id查询; 否则根据传入的memory 是否等于8,等于8,就按照memory查询;-->
<!--    //  否则根据disk来进行匹配-->
select *
from phone
<where><choose><when test="id &gt; 5">id = #{id}</when><when test="memory == 8">memory = #{memory}</when><otherwise>disk =#{disk}</otherwise></choose>
</where>;
</select>

10.4 sql-include

将一些公用的SQL,抽取出来。在其他需要使用的地方,可以直接引入这个公用SQL

一般都是用来提取一些公共列,然后再引入。

这是两个标签,配合起来一起使用的:

  • sql标签可以帮助我们把公共的sql提取出来
  • include可以帮助我们引入公共的sql片段。

提取

<!-- 相当于定义了一个变量,叫做 base_sql  内容是标签内的内容-->
<sql id="base_sql">select id, name, brand, memory, diskfrom phone
</sql>

引入

<select id="queryPhoneById" resultType="com.cskaoyan.demo5.bean.Phone"><!-- 可以使用include标签,将定义好的引入进来--><include refid="base_sql"/>where id = #{id}
</select>
<select id="queryAllPhone" resultType="com.cskaoyan.demo5.bean.Phone"><include refid="base_sql"/><where>and id = 1</where>;
</select>

优缺点

  • 优点:可以提取公共的sql片段,减少编码量。 防止数据库增加字段后,全部SQL需要修改
  • 缺点:用了sql-include 之后,SQL语句的可读性变差了

sql-include 标签我们一般用来提取 列名。

提取

<sql id="base_column">id, name, brand, memory, disk
</sql>

引入

<select id="queryPhoneById" resultType="com.cskaoyan.demo5.bean.Phone"><!-- 可以使用include标签,将定义好的引入进来-->    select <include refid="base_column" />from phone where id = #{id}
</select>

好处分析

  • 避免写select *
  • 不破坏SQL语句的可读性
  • 防止数据库增加字段后,全部SQL需要修改

10.5 trim

trim标签可以帮助我们动态的去增加指定的字符,或者是删除指定的字符。

  • prefix: 增加指定的前缀
  • suffix: 增加指定的后缀
  • prefixOverrides: 删除指定的前缀
  • suffixOverrides: 删除指定的后缀
@Test
public void testUpdatePhone(){
Phone phone = new Phone();
phone.setId(22);
phone.setMemory(16);int i = mapper.updatePhone(phone);
System.out.println(i);
}
int updatePhone(Phone phone);
<update id="updateByPhone">
update phone set
<!-- 会把包裹着的块,prefix/suffix prefixOverrides/suffixOverrides -->
<trim suffixOverrides="," >
<if test="brand != null">brand = #{brand},
</if>
<if test="memory != null">memory = #{memory},
</if>
<if test="disk != null">disk = #{disk}
</if>
</trim>
where id = #{id}
</update>

10.6 set

<set> 就相当于 <trim prefix="SET" suffixOverrides=","> 这个配置,这两个是等价的。

  • 去除set标签中的最后一个 “,”
  • 拼接set关键字
@Test
public void testTrim() {Phone phone = new Phone();phone.setId(5);phone.setBrand("honor");phone.setMemory(12);phone.setDisk(256);int i = mapper.updateByPhone(phone);System.out.println(i);
}
int updateByPhone(Phone phone);
<update id="updateByPhone">update phone<set><if test="brand != null">brand = #{brand},</if><if test="memory != null">memory = #{memory},</if><if test="disk != null">disk = #{disk}</if></set>where id = #{id}
</update>

10.7 foreach

foreach可以帮助我们去循环处理SQL语句。

10.8 批量插入

foreach在插入的使用

  • 当个方法中传入的参数没有注解的时候,假如传入的List,那么就可以使用 list,假如传入的是数组,那么就可以使用 array
  • 当方法中传入的参数有注解的时候,collection里面必须写注解的值

foreach遍历传入的集合

  • separator:循环的时候,以什么字符分割开
  • open:在循环开始的时候,添加指定的字符
  • close:在循环结束的时候,添加指定的字符
  • item:循环中的元素名 相当于 for(int i=0;i<100;i++) {} 中的 i
  • index: 元素的下标. index=“index1”代表使用一个叫做index1的变量将它存起来,在循环内部,就可以使用这个index1

10.8.1 List类型参数

没有注解
foreach的循环从插入的时候, 要求foreach标签的 collection参数, 是collection (如果List对象建议写list)

List<Phone> phones = new ArrayList<>();
phones.add(new Phone(null, "huawei mate50", "huawei", 8, 128));
phones.add(new Phone(null, "huawei mate40", "huawei", 8, 256));int i = mapper.insertPhoneList(phones);
System.out.println(i);
int insertPhoneList(List<Phone> phones);
<insert id="insertPhoneList">
<!-- insert into phone(id, name, brand, memory, disk) values (?, ?, ?, ?, ?) , (?, ?, ?, ?, ?) -->insert into phone(id, name, brand, memory, disk) values
<!-- foreach遍历传入的集合;separator:循环的时候,以什么字符分割开open:在循环开始的时候,添加指定的字符close:在循环结束的时候,添加指定的字符item:循环中的元素名 相当于 for(int i=0;i<100;i++) {}  中的 iindex: 元素的下标. index=“index1”代表使用一个叫做index1的变量将它存起来,在循环内部,就可以使用这个index1-->
<foreach collection="list" item="phone" separator=","open="" close="" index="index1">(#{phone.id}, #{phone.name}, #{phone.brand},#{phone.memory}, #{index1})
</foreach>
</insert>

10.8.2 数组类型参数

没有注解
foreach的循环从插入的时候, 要求foreach标签的 collection参数,必须是array

@Test
public void testInsertArray() {
Phone[] phones = new Phone[2];
phones[0] = new Phone(null, "huawei mate50", "huawei", 8, 128);
phones[1] = new Phone(null, "huawei mate40", "huawei", 8, 256);int i = mapper.insertPhoneArray(phones);
System.out.println(i);
}
int insertPhoneArray(Phone[] phones);
<insert id="insertPhoneArray">
insert into phone(id, name, brand, memory, disk) values
<foreach collection="array" item="phone" separator=","open="" close="" index="index1">(#{phone.id}, #{phone.name}, #{phone.brand},#{phone.memory}, #{index1})
</foreach>
</insert>

添加注解
如果在使用foreach的循环从插入的时候, 要求foreach标签的 collection参数, 必须是注解名

@Test
public void testInsertArray() {
Phone[] phones = new Phone[2];phones[0] = new Phone(null, "huawei mate50", "huawei", 8, 128);
phones[1] = new Phone(null, "huawei mate40", "huawei", 8, 256);int i = mapper.insertPhoneArray(phones);
System.out.println(i);
}@Test
public void testInsertArrayParam() {
List<Phone> phones = new ArrayList<>();phones.add(new Phone(null, "huawei mate50 ListParam", "huawei", 8, 128));
phones.add(new Phone(null, "huawei mate40 ListParam", "huawei", 8, 256));int i = mapper.insertPhoneListParam(phones);
System.out.println(i);
}
int insertPhoneArrayParam(@Param("phones") Phone[] phones);
int insertPhoneListParam(@Param("phones") List<Phone> phones);
<insert id="insertPhoneArrayParam">
insert into phone (id,name,brand,memory,disk) values
<foreach collection="phones" open=""close="" separator="," item="phone">(#{phone.id}, #{phone.name}, #{phone.brand},#{phone.memory}, #{phone.disk})
</foreach>
</insert><insert id="insertPhoneListParam">
insert into phone (id,name,brand,memory,disk) values
<foreach collection="phones" open=""close="" separator="," item="phone">(#{phone.id}, #{phone.name}, #{phone.brand},#{phone.memory}, #{phone.disk})
</foreach>
</insert>

10.9 使用in查询

foreach在查询时候的使用:

注意: foreach collection在不使用注解情况下, 默认集合类使用collection (List建议使用List), 数组使用array

如果参数使用了注解, foreach 标签的collection属性使用注解名

@Test
public void testQueryByIdList(){List<Phone> phones = mapper.queryPhoneByIdList(Arrays.asList(1,2,3));System.out.println(phones);
}
List<Phone> queryPhoneByIdList(@Param("list") List<Integer> list);
<select id="queryPhoneByIdList" resultType="com.cskaoyan.demo5.bean.Phone">
select *
from phone
<where>id in<foreach collection="list" separator="," open="(" close=")" item="id">#{id}</foreach></where>
</select>

10.10 selectKey

这个标签可以帮助我们在执行目标SQL语句之前或者是之后执行一条额外的SQL语句。有自动生成id的场景下,我们需要知道自动生成的id是多少。

AFTER操作

@Test
public void testInsertPhone(){Phone phone = new Phone();phone.setName("iphone13");phone.setBrand("Apple");int i = mapper.insertPhone(phone);System.out.println(phone);
}
int insertPhone(@Param("phone") Phone phone);
<insert id="insertPhone"><!--order: 表示在目标SQL执行之前或者是之后执行 AFTER | BEFOREkeyProperty: 表示执行的结果映射到哪个参数中resultType: SQL语句返回的类型 --><selectKey order="AFTER" keyProperty="phone.id" resultType="Integer">select LAST_INSERT_ID();</selectKey>insert into phone(id, name, brand, memory, disk) values(#{phone.id}, #{phone.name},#{phone.brand}, #{phone.memory}, #{phone.disk})</insert>

10.11 useGeneratedKeys

useGeneratedKeys: 获取insert/update操作数据的主键

开启配置:useGeneratedKeys=“true”
映射到对应的参数中:keyProperty=“order.id”

@Test
public void testInsertPhone2(){Phone phone = new Phone();phone.setName("iphone14");phone.setBrand("Apple");// 手机的id,会被存到原对象的id中int affectedRows = mapper.insertPhone2(phone);System.out.println(phone);
}
int insertPhone2(@Param("phone") Phone phone);
<insert id="insertPhone2" useGeneratedKeys="true" keyProperty="phone.id">insert into phone(id, name, brand, memory, disk)values (#{phone.id}, #{phone.name},#{phone.brand}, #{phone.memory}, #{phone.disk})
</insert>

11 多表查询

11.1 一对一结构

结构示例
在这里插入图片描述

drop table if exists user;
create table user(id int primary key auto_increment,name varchar(255),email varchar(255),password varchar(255)
);drop table if exists user_detail;
create table user_detail(id int primary key auto_increment,
user_id int,address varchar(255),pic varchar(255)
);insert into user values (1, "猪八戒","zhubajie@qq.com", "zhubajie");
insert into user values (2, "孙悟空","sunwukon@qq.com", "sunwukong");
insert into user values (3, "白骨精","baigujin@qq.com" ,"baigujing");
insert into user values (4, "唐僧",  "tangsen@qq.com" , "tangseng");
insert into user values (5, "沙僧", "shaseng@qq.com", "shaseng");select * from user;insert into user_detail values(null, 1, "高老庄", "猪八戒.jpg");
insert into user_detail values(null, 2, "花果山", "孙悟空.jpg");
insert into user_detail values(null, 3, "白虎岭", "白骨精.jpg");
insert into user_detail values(null, 4, "东土大唐", "唐僧.jpg");select * from user_detail;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {private Integer id;private String name;private String email;private String password;private UserDetail userDetail;
}@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDetail {private Integer id;private Integer userId;private String address;private String pic;
}

11.1.1 方式一: 分次查询

测试

@Test
public void testQueryOne2One() {List<User> users = mapper.queryAllUser();for (User user : users) {System.out.println(user);}
}

TableMapper接口

List<User> queryAllUser();

UserMapper.xml文件

<mapper namespace="com.cskaoyan.demo9.mapper.UserMapper"><resultMap id="baseUserMap" type="com.cskaoyan.demo9.bean.User"><id column="id" property="id"/><result column="name" property="name"/><result column="email" property="email"/><result column="password" property="password"/><!-- association: 关联单个bean的时候,使用property: 成员变量的名字javaType: 成员变量的类型select: 第二条SQL执行时,SQL的坐标.如果两条SQL在一个mapper.xml中,可以不写namespace。如果不在一个mapper.xml中,需要写namespacecolumn: 第二条SQL,传递的参数--><association property="userDetail" javaType="com.cskaoyan.demo9.bean.UserDetail"column="id"select="com.cskaoyan.demo9.mapper.UserDetailMapper.queryUserDetailByUserId"/></resultMap><select id="queryAllUser" resultMap="baseUserMap">select * from user;</select>
</mapper>

UserDetailMapper.xml文件

<mapper namespace="com.cskaoyan.demo9.mapper.UserDetailMapper">
<select id="queryUserDetailByUserId" resultType="com.cskaoyan.demo9.bean.UserDetail">select *from user_detailwhere user_id = #{userId};</select>
</mapper>

11.1.2 方式二: 连接查询

测试

@Test
public void testQueryOne2One2(){
List<User> users = mapper.queryAllUser2();
for (User user : users) {System.out.println(user);
}
}

UserMapper接口

List<User> queryAllUser2();

UserMapper.xml

<resultMap id="baseUserMap2" type="com.cskaoyan.demo9.bean.User">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="email" property="email"/>
<result column="password" property="password"/><association property="userDetail"javaType="com.cskaoyan.demo9.bean.UserDetail"><id column="ud_id" property="id"/><result column="pic" property="pic"/><result column="user_id" property="userId"/><result column="address" property="address"/>
</association>
</resultMap><select id="queryAllUser2" resultMap="baseUserMap2">
select u.id       as id
, u.name     as name
, u.email    as email
, u.password as password
, ud.id      as ud_id
, ud.pic     as pic
, ud.user_id as user_id
, ud.address as address
from user u
left join user_detail ud on u.id =
ud.user_id;
</select>

11.2 一对多结构

结构示例
在这里插入图片描述

drop table if exists student;
drop table if exists class;create table class(id int primary key auto_increment,name varchar(200)
);create table student(id int primary key auto_increment,name varchar(200),age int,class_id int
);insert into class values(1,"一班");
insert into class values(2,"二班");
insert into class values(3,"三班");insert into student values(1, "张飞", 30, 1);
insert into student values(2, "关羽", 40, 1);
insert into student values(3, "李云龙", 35, 2);
insert into student values(4, "楚云飞", 33, 2);
insert into student values(5, "王有胜", 30, 2);
insert into student values(6, "林冲", 30, 3);
insert into student values(7, "孙二娘", 35, 4);select * from class;
select * from student;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Clazz {
private Integer id;
private String className;private List<Student> studentList;
}@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
private Integer id;
private String name;
private Integer age;
private Integer classId;
}

11.2.1 方式一: 分次查询

测试

@Test
public void test1() {
List<Clazz> clazzes = mapper.queryAllClazz();
for (Clazz clazz : clazzes) {System.out.println(clazz);
}
}

ClazzMapper接口

List<Clazz> queryAllClazz();

ClazzMapper.xml

<resultMap id="baseClazzMap" type="com.cskaoyan.demo9.bean.Clazz">
<id column="id" property="id"/>
<result column="name" property="name"/>
<!--collection: 关联的是集合,用collectionproperty:成员变量的名字ofType:集合中单个元素的类型select: 关联的SQL语句坐标column: 列名 --><collection property="studentList"ofType="com.cskaoyan.demo9.bean.Student"select="com.cskaoyan.demo9.mapper.StudentMapper.queryStudentListByClassId"column="id">
</collection>
</resultMap><select id="queryAllClazz" resultMap="baseClazzMap">
select *
from class;
</select>

StudentMapper.xml文件

<resultMap id="baseResultMap" type="com.cskaoyan.demo9.bean.Student">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
<result column="class_id" property="classId"/>
</resultMap><select id="queryStudentListByClassId" resultMap="baseResultMap">
select *
from student
where class_id = #{classId};
</select>

11.2.2 方式二: 连接查询

测试

@Test
public void test2(){
List<Clazz> clazzes = mapper.queryAllClazz2();
for (Clazz clazz : clazzes) {System.out.println(clazz);
}
}

ClazzMapper接口

List<Clazz> queryAllClazz2();

TableMapper.xml

<resultMap id="baseResultMap2" type="com.cskaoyan.demo9.bean.Clazz">
<id column="id" property="id"/>
<result column="name" property="name"/>
<collection property="studentList" ofType="com.cskaoyan.demo9.bean.Student"><id column="s_id" property="id"/><result column="s_name" property="name"/><result column="s_age" property="age"/><result column="s_class_id" property="classId"/>
</collection>
</resultMap><select id="queryAllClazz2" resultMap="baseResultMap2">
select c.id,
c.name,
s.id       as s_id,
s.name     as s_name,
s.age      as s_age,
s.class_id as s_class_id
from class c
left join student s on c.id = s.class_id;
</select>

11.3 多对多结构

结构示例

请添加图片描述

drop table if exists tec_stu;
drop table if exists tec_course;
drop table if exists tec_sele_course;create table tec_stu(id int primary key auto_increment,name varchar(200)
);create table tec_course(id int primary key auto_increment,
name varchar(200)
);-- 选课表
create table tec_sele_course(id int primary key auto_increment,
student_id int,
course_id int
);insert into tec_stu values (1, "李云龙");
insert into tec_stu values (2, "楚云飞");
insert into tec_stu values (3, "赵刚");
insert into tec_stu values (4, "王有胜");
insert into tec_stu values (5, "孙悟空");insert into tec_course values (1, "Java");
insert into tec_course values (2, "C++");
insert into tec_course values (3, "Python");insert into tec_sele_course(student_id, course_id) values (1,1);
insert into tec_sele_course(student_id, course_id) values (1,2);
insert into tec_sele_course(student_id, course_id) values (2,2);
insert into tec_sele_course(student_id, course_id) values (2,3);
insert into tec_sele_course(student_id, course_id) values (3,1);
insert into tec_sele_course(student_id, course_id) values (4,2);
insert into tec_sele_course(student_id, course_id) values (5,3);select * from tec_stu;
select * from tec_cource;
select * from tec_sele_course;
@Data
public class TecCourse {private Integer id;private String name;List<TecStu> studentList;
}@Data
public class TecStu {private Integer id;private String name;
}

11.3.1 方式一: 分次查询

根据用户的名字查询出用户信息以及用户对应购买的商品信息。

测试

@Test
public void testQueryAllCourses1() {List<TecCourse> tecCourses = mapper.queryAllCourses2();for (TecCourse tecCours : tecCourses) {System.out.println(tecCours);}
}

Mapper接口

List<TecCourse> queryAllCourses1();

Mapper.xml

<select id="queryByCourseId" resultType="com.cskaoyan.demo11.bean.TecStudent">select s.id,s.name from tec_sele_course sc left join tec_stu s on sc.student_id = s.idwhere sc.course_id = #{courseId}
</select><resultMap id="baseCourseResultMap1" type="com.cskaoyan.demo11.bean.TecCourse"><id column="id" property="id"/><result column="name" property="name"/><collection property="students" ofType="com.cskaoyan.demo11.bean.TecStudent"column="id"select="com.cskaoyan.demo11.mapper.TecStudentMapper.queryByCourseId"></collection>
</resultMap><select id="queryAllCourses1" resultMap="baseCourseResultMap1">select id, namefrom tec_course;
</select>

11.3.2 方式二: 连接查询

测试

@Test
public void testQueryAllCourses2() {
List<TecCourse> tecCourses = mapper.queryAllCourses2();for (TecCourse tecCours : tecCourses) {System.out.println(tecCours);
}
}

Mapper接口

List<TecCourse> queryAllCourses2();

Mapper.xml

<resultMap id="baseCourseResultMap2" type="com.cskaoyan.demo11.bean.TecCourse">
<id column="c_id" property="id"/>
<result column="c_name" property="name"/>
<collection property="students" ofType="com.cskaoyan.demo11.bean.TecStudent"><id column="s_id" property="id"/><result column="s_name" property="name"/>
</collection>
</resultMap><select id="queryAllCourses2" resultMap="baseCourseResultMap2">
select c.id as c_id, c.name as c_name, s.id as s_id, s.name as s_name
from tec_course c
left join tec_sele_course sc on c.id = sc.course_id
left join tec_stu s on sc.student_id = s.id;
</select>

12 懒加载

懒加载又叫做延迟加载。

懒加载是指在Mybatis进行分次查询的时候,假如第二次查询的内容没有被使用到的话,那么就不去执行第二次查询的SQL语句,等到用到第二次查询的内容的时候再去执行第二条SQL语句。

注意:

  1. 当局部开关配置的时候,以局部开关的配置为准
  2. 当局部开关没有配置的时候,以总开关的配置为准
  3. 当总开关也没有配置的时候,以默认配置为准(默认配置是关闭懒加载)

总开关配置: mybatis的主配置文件里面的settings里面

<settings>
<!-- 懒加载 true: 表示开启  false:默认值,表示关闭 --> 
<setting name="lazyLoadingEnabled" value="true"/>
</settings>

12.1 案例

测试

// 测试
List<User> list = mapper.selectUserGoodsListByName("天明");

Mapper接口

// Mapper接口
List<User> selectUserGoodsListByName(String name);

Mapper.xml

<select id="selectUserGoodsListByName" resultMap="baseMap5">
select id,  user_name  from user where user_name = #{name}
</select>
<resultMap id="baseMap5" type="bean.User">
<id column="id" property="id"/>
<result column="user_name" property="userName"/>
<collection property="goodsList"ofType="bean.Goods"select="selectGoodsListByUserId"column="id"/>
</resultMap><select id="selectGoodsListByUserId" resultType="bean.Goods">
select g.id , g.goods_name as goodsName
from `order` o left join goods g on g.id = o.goods_id
where o.user_id = #{id}
</select>  

局部开关

<!-- fetchType: eager关闭/lazy开启 -->
<resultMap id="baseMap5" type="bean.User">
<id column="id" property="id"/>
<result column="user_name" property="userName"/>
<collection property="goodsList"ofType="bean.Goods"select="selectGoodsListByUserId"fetchType="lazy"column="id"/>
</resultMap>

注意:idea的Debug模式下不能复现懒加载,因为debug模式会显示出对象中的所有的信息,相当于已经用到了第二次SQL语句查询的内容,所以第二次SQL语句就会立马执行

13 缓存

  • 缓存是指在Mybatis中,单独开辟一块内存空间(map),来存储查询的信息。后续假如再次调用了到了同样的查询,那么就直接查询缓存。

  • MyBatis默认开启了缓存

MyBatis是怎么存储缓存的: 在MyBatis中缓存是以Map(集合类容器)接口存储的。
map:

  • key:SQL语句和查询的条件(注意: SQL语句是依赖于坐标的) (MapperID+Sql+所有的入参)
  • value:查询的结果

13.1 一级缓存

一级缓存是一个以SqlSession管理的Mapper级别的缓存。缓存的内容存储在SQLSession中管理。

一定要通过同一个SqlSession获取出来的Mapper,才会走缓存
在这里插入图片描述

配置
一级缓存默认是开启的,并且没有提供开关给用户关闭(不可以关闭)。

一级缓存什么时候失效呢?

  • SqlSession关闭的时候
  • SqlSession调用增删改的时候,会清空当前SqlSession缓存
  • SqlSession调用commit方法

13.1.1 测试

  • 同一个SqlSession 获取的同一个Mapper: 走缓存
@Test
public void testQueryByOneMapper1() {SqlSession sqlSession = MybatisUtil.getSqlSession(true);StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);Student student = mapper.queryStudentByPrimaryKey(1);// 根据同一个Mapper。会走缓存Student student1 = mapper.queryStudentByPrimaryKey(1);Student student2 = mapper.queryStudentByPrimaryKey(1);System.out.println(student);// 参数不同,不会走缓存Student student5 = mapper.queryStudentByPrimaryKey(2);System.out.println(student5);
}
  • 同一个SQLSession获取不同的的mapper: 走缓存
@Test
public void testQueryByTwoMapper1() {SqlSession sqlSession = MybatisUtil.getSqlSession(true);// 同一个SqlSession获取的不同Mapper。会走缓存StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);Student student = mapper.queryStudentByPrimaryKey(1);StudentMapper mapper2 = sqlSession.getMapper(StudentMapper.class);Student student1 = mapper2.queryStudentByPrimaryKey(1);
}
  • 不同SQLSession获取同一种的mapper: 不走缓存
@Test
public void testQueryByTwoSqlSession() {SqlSession sqlSession = MybatisUtil.getSqlSession(true);SqlSession sqlSession2 = MybatisUtil.getSqlSession(true);// 不同SqlSession获取的Mapper。不会走缓存StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);StudentMapper mapper2= sqlSession2.getMapper(StudentMapper.class);Student student = mapper.queryStudentByPrimaryKey(1);Student student2 = mapper2.queryStudentByPrimaryKey(1);
}

13.2 二级缓存

二级缓存是一个NameSpace级别(mapper.xml)的缓存,每一个NameSpace都有自己的单独的缓存空间。(要通过两级配置开启)

  • 配置1:总开关
<!-- 二级缓存开关配置 -->
<setting name="cacheEnabled" value="true"/>
  • 配置2: 局部开关
    在这里插入图片描述

  • 需要对二级缓存的缓存的所有相关对象实现序列化接口

  1. 开启自动生成序列化id

在这里插入图片描述

  1. 实现序列化接口,生成序列化id

在这里插入图片描述

  1. 二级缓存是 namespace级别/Mapper级别 的缓存
  2. 多个SqlSession可以共用二级缓存(同一个Mapper)
  3. 在关闭sqlsession后(close); 才会把该sqlsession一级缓存中的数据添加到对应namespace的二级缓存中。
  4. Mybatis默认先查询二级缓存,二级缓存中无对应数据,再去查询一级缓存,一级缓存中也没有,最后去数据库查找。

在这里插入图片描述

13.2.2 测试

测试

@Test
public void testQueryTwoLevelCache() {
SqlSession sqlSession = MybatisUtil.getSqlSession(true);
SqlSession sqlSession2 = MybatisUtil.getSqlSession(true);// 不同SqlSession获取的Mapper。不会走缓存
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
StudentMapper mapper2 = sqlSession2.getMapper(StudentMapper.class);Student student = mapper.queryStudentByPrimaryKey(1);
Student student2 = mapper2.queryStudentByPrimaryKey(1);// 二级缓存是在namespace级别下的缓存
// 只有close后,才会把数据添加进二级缓存
sqlSession.close();Student student1 = mapper2.queryStudentByPrimaryKey(1);
System.out.println(student1);}

二级缓存有没有用呢?

  • 其实有一定的作用,但是也有一定的缺陷

  • 确实能够提高Mybatis的性能

  • 不能完美的解决脏数据的问题

  • 二级缓存空间对于用户来说是完全透明的,我们用户不能够直接的去操作它,也不能够让用户指定去查询数据库还是查询缓存,所以其实使用起来不太方便

在工作中,有一些需要使用缓存的场景,那么这个时候我们不会考虑使用Mybatis给我们提供的缓存,取而代之的是使用NoSQL数据库(Redis)

这篇关于Mybatis学习与使用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1145241

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

mybatis的整体架构

mybatis的整体架构分为三层: 1.基础支持层 该层包括:数据源模块、事务管理模块、缓存模块、Binding模块、反射模块、类型转换模块、日志模块、资源加载模块、解析器模块 2.核心处理层 该层包括:配置解析、参数映射、SQL解析、SQL执行、结果集映射、插件 3.接口层 该层包括:SqlSession 基础支持层 该层保护mybatis的基础模块,它们为核心处理层提供了良好的支撑。

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传