前传:JDBC
一、JDBC程序访问数据库步骤
二、PreparedStatement优化掉Statement
1、通过Statement对象访问数据库
首先加载并注册驱动程序
①调用DriverManager.getConnection(url,user,password)方法
创建Connection对象
②调用Connection对象的createStatement()方法
创建Statement对象
③定义类型为String的sql变量,内容为SQL语句
④(查询)使用ResulSet对象、关闭ResulSet对象
⑤从下往上释放资源,结束
2、通过PreparedStatement对象访问数据库
首先加载并注册驱动程序
①调用DriverManager.getConnection(url,user,password)方法
创建Connection对象
②定义类型为String的sql变量,内容为带有 ? SQL语句,
③调用Connection对象的preparedStatement(sql)方法创建预编译对象
对sql进行预编译,创建PreparedStatement对象
④调用PreparedStatement对象的setXX方法把?用具体值进行代替
⑤调用PreparedStatement对象的executeXX方法,创建len对象
⑥打印输出len的值(采用boolen语句)
⑦从下往上释放资源,结束
三、Phoenix JDBC的使用(底层为HBase)
首先要注册驱动,但不需要我们去写
与上面的操作几乎一模一样
url是Phoenix JDBC限定格式,官网上有
四、数据库连接池——优化掉DriverManager.getConnection()方法
例如使用Druid连接池
①导入德鲁伊jar包,或者在pom中获取德鲁伊依赖(Maven)
②在src目录下创建db.properties类型配置文件,设置数据库连接池信息
url=jdbc:mysql://localhost:3306/db2 username=root password=root driverClassName=com.mysql.jdbc.Driver initialSize=10 maxActive=20 maxWait=1000
③读取properties内容
Properties properties = new Properties();
④通过类加载器读取类路径下内容
pro.load(TestDruid.class.getClassLoader().getResourceAsStream("druid.properties"));
DataSource ds = DruidDataSourceFactory.createDataSource(pro);
⑤通过返回数据源对象,获取数据库连接
Connection conn = ds.getConnection();
System.out.println(conn);//测试获取超过最大连接数的连接数量(连接关闭、未关闭的情况)
五、ThreadLocal解决多线程并发
ThreadLocal用于解决多线程程序的并发问题,作用是保存某个线程共享变量
1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。
2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。
3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。
那该如何把ThreadLocal用进去呢?
六、封装工具类JDBCUtil:
1、集成Druid、ThreadLocal、close()
public class JdbcUtils { //定义成员变量 private static DataSource dataSource; private static ThreadLocal<Connection> threadLocal; //静态代码块 static { try { //加载连接池配置文件,创建DataSource Properties properties = new Properties(); properties.load(JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties")); dataSource = DruidDataSourceFactory.createDataSource(properties); //初始化threadLocal对象 threadLocal = new ThreadLocal<>(); } catch (Exception e) { e.printStackTrace(); } } //获取数据库连接池连接 public static Connection getConnection() { //1 ThreadLocal可以把线程和连接绑定在一起 //从ThreadLocal获取连接 Connection connection = threadLocal.get(); //如果获取不到,dataSource取出来,放到ThreadLocal里面 if(connection == null) { try { connection = dataSource.getConnection(); threadLocal.set(connection); } catch (SQLException e) { e.printStackTrace(); } } //如果获取到,直接返回 return connection; } //释放资源 public void closeResource() { Connection connection = threadLocal.get(); if(connection != null) { try { //close() connection.close(); threadLocal.remove(); } catch (SQLException e) { e.printStackTrace(); } } } }
2、使用我们封装的JDBCUtil工具类,优化以上的访问数据库操作
public class TestUtils { public static void main(String[] args) { PreparedStatement preparedStatement = null; ResultSet rs = null; try { //获取数据库连接 Connection connection = JdbcUtils.*getConnection*(); String sql = "select from dept"; preparedStatement = connection.prepareStatement(sql); rs = preparedStatement.executeQuery(); hile(rs.next()) { System.out.println(rs.getString(**"dname"**)); } } catch (SQLException e) { e.printStackTrace(); } finally { try** { rs.close(); preparedStatement.close(); JdbcUtils.closeResource(); } catch (SQLException e) { e.printStackTrace(); } } } }
上面的JDBCUtil只是简单的工具类
七、Apache提供了更完整的工具类封装——Apache-DBUtils
—————————分割线——————————
正传:DBUtils被MyBatis取代
一、MyBatis简介
1、MyBatis历史
MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁 移到了Google Code。随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis。代码于 2013年11月迁移到Github。
iBatis一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。 iBatis提供的持久层框架 包括SQL Maps和Data Access Objects(DAO)。
2、MyBatis特性
1)MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架
2)MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
3)MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java和POJO(Plain Old Java Objects, 普通的Java对象)映射成数据库中的记录
4)MyBatis 是一个半自动的 ORM(Object Relation Mapping) 框架
3、MyBatis下载
4、和其它持久层技术对比
-
JDBC
-
-
SQL 夹杂在Java代码中耦合度高,导致硬编码内伤
-
维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见
-
-
-
代码冗长,开发效率低
-
-
Hibernate 和 JPA
-
-
操作简便,开发效率高
-
程序中的长难复杂 SQL 需要绕过框架
-
-
-
内部自动生产的 SQL,不容易做特殊优化 基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难。
-
反射操作太多,导致数据库性能下降
-
-
MyBatis
-
-
轻量级,性能出色
-
SQL和Java编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据
-
-
-
开发效率稍逊于Hlbernate,但是完成能够接受
-
二、搭建MyBatis
1、开发环境
IDE:idea 2019.2
构建工具:maven 3.5.4
MySQL版本:MySQL 5.7
MyBatis版本:MyBatis 3.5.7
2、创建maven工程
a>打包方式
<packaging>jar</packaging>
b>引入依赖
<dependencies> <dependency> <!--Mybatis核心--> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency> <!--junit测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!--mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.27</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.22</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies>
3、创建MyBatis的核心配置文件
习惯上命名为mybatis-config.xml,这个文件名仅仅只是建议,并非强制要求。将来整合Spring
之后,这个配置文件可以省略,所以大家操作时可以直接复制、粘贴。 核心配置文件主要用于配置连接数据库的环境以及MyBatis的全局配置信息
核心配置文件存放的位置是src/main/resources目录下
<?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/MyBatis"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <!--引入映射文件--> <mappers> <mapper resource="mappers/UserMapper.xml"/> </mappers> </configuration>
4、创建mapper接口
MyBatis中的Mapper接口相当于以前的dao。但是区别于,mapper仅仅是接口,我们不需要听实现类
public interface UserMapper { /** * Mybatis面向接口编程的两个一致 * 1.映射文件的namespace要和mapper接口的全类名接口保持一致 * 2.映射文件的id要和mapper接口中的方法保持一致 * * 表 -- 实体类 -- mapper接口 -- 映射文件 */ /** * 添加用户信息 */ int insertUser(User user); /** * 修改用户信息 */ void updateUser(); /** * 删除用户信息 */ void deleteUser(); /** * 根据id查询用户信息 */ User getUserById(); /** * 查询所有的用户信息 */ List<User> getAllUser(); }
5、创建MyBatis的映射文件
相关概念:ORM(Object Relationship Mapping)对象关系映射。
-
对象:Java的实体类对象
-
关系:关系型数据库
-
映射:二者之间的对应关系
1、映射文件的命名规则:
表所对应的实体的类名+Mapper.xml
例如:表t_user,映射的实体类为User,所对应的映射文件为UserMapper.xml
因此一个映射文件用于编写SQL,访问以及操作表中的数据
MyBaits映射文件用于编写SQL,访问以及操作表中的数据
MyBatis映射文件存放的位置编写SQL,访问以及操作表中的数据
2、MyBatis中可以面向接口操作数据,要保存两个一致:
a>mapper接口的全类名和映射文件的命名空间(namespace)保持一致
b>mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致
<?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.atguigu.mybatis.mapper.UserMapper"> <!--int insertUser();--> <insert id="insertUser"> insert into t_user values(null,'张三','123',23,'女') </insert> </mapper>
6、通过junit测试功能
@Test public void testMyBatis() throws IOException { // 加载核心配置文件 InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); // 获取SqlSessionFactoryBuilder SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); // 获取SqlSessionFactory SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is); // 获取SqlSession SqlSession session = sqlSessionFactory.openSession(true); // 获取mapper接口对象 UserMapper mapper = session.getMapper(UserMapper.class); // 底层会自动为我们创建好mapper接口的实现类 // 测试功能 int result = mapper.insertUser(new User(null, "小胡", "123456", 20, "男", "123@qq.com")); // 提交事务 // session.commit(); System.out.println("result = " + result); session.close(); }
-
SqlSession:代表java程序和数据库之间的会话。(HttpSession是java程序和浏览器之间的会话)
-
SqlSessionFactory:是生产SqlSession的工厂
-
工厂模式:如果创建某个对象,使用的过程基本固定,那么我们就可以把创建这个对象的相关代码封装到一个”工厂类“中,以后都可以使用这个工厂类来”生产”我们需要的对象。
7、加入log4j日志功能
a>加入依赖
<!-- log4j日志 --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
b>加入log4j的配置文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender"> <param name="Encoding" value="UTF-8" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" /> </layout> </appender> <logger name="java.sql"> <level value="debug" /> </logger> <logger name="org.apache.ibatis"> <level value="info" /> </logger> <root> <level value="debug" /> <appender-ref ref="STDOUT" /> </root> </log4j:configuration>
日志的级别
FATAL(致命)>ERROR(错误)>WARN(警告)>INFO(信息)>DEBUG(调试) 从左到右打印的内容越来越详细
三、核心配置文件详解
核心配置文件中标签必须按照固定的顺序:
properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorF actory?,plugins?,environments?,databaseIdProvider?,mappers?
<?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> <!-- MyBatis核心配置文件中,标签的顺序: properties?,settings?,typeAliases?,typeHandlers?,objectFactory?, objectWrapperFactory?,reflectorFactory?,plugins?, environments?,databaseIdProvider?,mappers? --> <!--引入properties配置文件--> <properties resource="jdbc.properties" /> <!--设置类型别名--> <typeAliases> <!-- typeAlias:设置某个类型的别名 属性: type:设置需要设置别名的类型 alas:设置某个类型的别名,若不设置该属性,那么该类型拥有默认的别名,即类型且不区分大小写 --> <!--<typeAlias type="com.atguigu.mybatis.pojo.User" />--> <!--以包为单位,将包下所有的类型设置默认的类型别名,即类名且不区分大小写--> <package name="com.atguigu.mybatis.pojo"/> </typeAliases> <!-- environments:配置多个连接数据库的环境 属性: default:设置默认使用的环境的id --> <environments default="development"> <!-- environment:配置某个具体的环境 属性: id:表示连接数据库的环境的唯一标识,不能重复 --> <environment id="development"> <!-- transactionManager:设置事务管理方式 属性: type="JDBC|MANAGER" JDBC:表示当前环境中,执行SQL中,使用的是JDBC中原生的事务管理方式,事务的提交或回滚需要手动提交 MANAGER:被管理,例如spring --> <transactionManager type="JDBC"/> <!-- dataSource:配置数据源 属性: type:设置数据源的类型 type="POOLED|UNPOOLED|JNDI" POOLED: 表示使用数据库连接池缓存数据库连接 UNPOOLED: 表示不适用数据库连接池 JNDI: 表示使用上下文中的数据源 --> <dataSource type="POOLED"> <!--driver:驱动内容--> <property name="driver" value="${jdbc.driver}"/> <!--连接数据库的url--> <property name="url" value="${jdbc.url}"/> <!--用户名--> <property name="username" value="${jdbc.username}"/> <!--密码--> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> <environment id="test"> <transactionManager type="JDBC"/> <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> <!--引入映射文件--> <mappers> <!--<mapper resource="mappers/UserMapper.xml" />--> <!-- 以包为单位引入映射文件 要求: 1、mapper接口所在的包要和映射文件所在的包一致 2、mapper接口和映射文件的名字一致 --> <package name="com.atguigu.mybatis.mapper"/> </mappers> </configuration>
四、MyBatis的增删改查
1、添加
/** * 添加用户信息 */ int insertUser(User user); <!--int insertUser(User user);--> <insert id="insertUser"> insert into t_user values(null, #{username}, #{password}, #{age}, #{sex}, #{email}) </insert>
2、删除
/** * 删除用户信息 */ void deleteUser(); <!--void deleteUser()--> <delete id="deleteUser"> delete from t_user where id = 3 </delete>
3、修改
/** * 修改用户信息 */ void updateUser(); <!--void updateUser();--> <update id="updateUser"> update t_user set username = '张三' where id = 4 </update>
4、查询一个实体对象
/** * 根据id查询用户信息 */ User getUserById(); <!-- 查询功能的标签必须设置resultType或者resultMap resultType:设置默认的映射关系 resultMap:设置上自定义的映射关系 字段名和实体属性不一致的时候 --> <select id="getUserById" resultType="com.atguigu.mybatis.pojo.User"> select * from t_user where id = 4 </select>
5、查询集合
/** * 查询所有的用户信息 */ List<User> getAllUser(); <!--List<User> getAllUser();--> <select id="getAllUser" resultType="User"> select * from t_user </select>
注意:
1、查询的标签select必须设置属性resultType或resultMap,用于设置实体类和数据库表的映射关系
resultType:自动映射,用于属性名和表中字段名一致的情况
resultMap:自定义映射,用于一对多或多对一或字段名和属性名不一致的情况
2、当查询的数据我多条时,不能使用实体类作为返回值,只能使用集合,否则会抛出异常
TooManyResultsException;但是若查询的数据只有一条,可以使用实体类或集合作为返回值
五、MyBatis获取参数值的两种方式(重点)
MyBatis获取参数值的两种方式:${} 和 #{}
${} 的本质就是字符串拼接,#{}的本质就是占位符赋值
${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号;但是#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,可以自动添加单引号
1、单个字面量类型的参数
若mapper接口中的方法参数为单个的字面量类型
此时可以使用${}和#{}以任意的名称获取参数的值,注意${}需要手动加单引号
/** * 根据用户名查询用户信息 */ User getUserByUsername(String username); <!--User getUserByUsername(String username);--> <select id="getUserByUsername" resultType="User"> <!-- select * from t_user where username = #{username} --> select * from t_user where username = '${username}' </select>
##
2、多个字面量类型的参数
若mapper接口中的方法参数为多个时
此时MyBatis会自动将这些参数放在一个map集合中,以arg0,arg1...为键,以参数为值;以 param1,param2...为键,以参数为值;因此只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号
/** * 验证登录 */ User checkLogin(String username, String password); <!--User checkLogin(String username, String password);--> <!--[arg0,arg1,param1,param2]--> <select id="checkLogin" resultType="User"> <!-- select * from t_user where username = #{agr0} and password = #{arg1} --> select * from t_user where username = '${param1}' and password = '${param2}' </select>
3、map集合类型的参数
若mapper接口中的方法采参数为多个时,此时可以手动创建map集合,将这些数据放在map中只需要通过${}和#{}访问map集合的键就可以获取对应的值,注意${}需要手动加单引号
/** * 验证登录(参数为map) */ User checkLoginByMap(Map<String,Object> map); <!--User checkLoginByMap(Map<String,Object> map);--> <select id="checkLoginByMap" resultType="user"> select * from t_user where username = #{username} and password = #{password} </select>
4、实体类类型的参数
若mapper接口中的方法参数为实体类对象时 此时可以使用${}和#{},通过访问实体类对象中的属性名获取属性值,注意${}需要手动加单引号
/** * 添加用户信息 */ int insertUser(User user); <!--int insertUser(User user);--> <insert id="insertUser"> insert into t_user values(null, #{username}, #{password}, #{age}, #{sex}, #{email}) </insert>
5、使用@Param标识参数
可以通过@Param注解标识mapper接口中的方法参数
此时,会将这些参数放在map集合中,以@Param注解的value属性值为键,以参数为值;以 param1,param2...为键,以参数为值;只需要通过${}和#{}访问map集合的键就可以获取相对应的值, 注意${}需要手动加单引号
/** * 验证登录(使用@Parm) */ User checkLoginByParma(@Param("username") String username,@Param("password") String password); <!--User checkLoginByParma(@Param("username") String username,@Param("password") String password);--> <select id="checkLoginByParma" resultType="User"> select * from t_user where username = #{username} and password = #{password} </select>
测试类
package com.atguigu.mybatis.test; import com.atguigu.mybatis.mapper.ParameterMapper; import com.atguigu.mybatis.pojo.User; import com.atguigu.mybatis.utils.SqlSessionUtils; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import java.util.HashMap; import java.util.List; import java.util.Map; /** * * date: 2022/02/28 * Description: */ public class ParameterMapperTest { /** * MyBatis获取参数值的两种方式:${} 和 #{} * ${} 本质是字符串拼接 (可能会导致sql注入) * #{} 本质是占位符赋值 * MyBatis获取参数值的各种情况: * 1、mapper接口方法的参数为单个的字面量类型 * 可以通过${}和#{}以任意的名称获取参数值,但是需要注意${}的单引号问题 * 2、mapper接口方法的参数为多个时 * 此时MyBatis会将这些参数放在一个map集合中,以两种方式进行存储 * a>以arg0,arg1..为键,以参数为值 * b>以param1,param2...为键,以参数为值 * 因此只需要通过#{}和${}以键的方式访问值即可,但是需要注意${}的单引号问题 * 3、若mapper接口方法的参数有多个时,可以手动将这些参数放在一个map中存储 * 只需要通过#{}和${}以键(自己设置的)的方式访问值即可,但是需要注意${}的单引号问题 * 4、mapper接口方法的参数是一个实体类类型的参数 * 只需要通过#{}和${}以属性的方式访问属性值即可,但是需要注意${}的单引号问题 * 5、使用@Param注解命名参数 * 此时MyBatis会将这些参数放在一个map集合汇总,以两种方式进行存储 * a>以@Param注解的值为键,以参数为值 * b>以param1,param2...为键,以参数为值 * 因此只需要通过#{}和${}以键的方式访问值即可,但是需要注意${}的单引号问题 */ @Test public void testInsertUser() { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class); int rows = mapper.insertUser(new User(null, "李四", "123", 23, "男", "123@qq.com")); System.out.println(rows); } @Test public void testCheckLoginByMap() { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class); Map<String,Object> map = new HashMap<>(); map.put("username", "admin"); map.put("password", "123456"); User user = mapper.checkLoginByMap(map); System.out.println(user); } @Test public void testCheckLoginByParam() { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class); User user = mapper.checkLoginByParma("张三", "123456"); System.out.println(user); sqlSession.close(); } @Test public void testCheckLogin() { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class); User user = mapper.checkLogin("张三", "123456"); System.out.println(user); } @Test public void testGetUserByUserName() { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class); User user = mapper.getUserByUsername("张三"); System.out.println(user); } @Test public void testGetAllUser() { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class); List<User> users = mapper.getAllUser(); users.forEach(user -> System.out.println(user)); } }
六、MyBatis的各种查询功能
1、查询一个实体类对象
/** * 根据id查询用户的信息 */ List<User> getUserById(@Param("id") Integer id); <!--List<User> getUserById(@Param("id") Integer id);--> <select id="getUserById" resultType="User"> select * from t_user where id = #{id} </select>
2、查询一个list集合
/** * 查询所有的用户信息 */ List<User> getAllUsers(); <!--List<User> getAllUsers();--> <select id="getAllUsers" resultType="User"> select * from t_user </select>
3、查询单个数据
/** * 查询用户信息的总记录数 */ Integer getCount(); <!--Integer getCount();--> <select id="getCount" resultType="integer"> select count(*) from t_user </select>
4、查询一条数据为map集合
/** * 根据id查询用户信息为一个map集合 */ Map<String, Object> getUserByIdToMap(@Param("id") Integer id); <!--Map<String, Object> getUserByIdToMap(@Param("id") Integer id);--> <select id="getUserByIdToMap" resultType="map"> select * from t_user where id = #{id} </select> <!--结果:{password=123456, sex=男, id=1, age=23, username=admin}-->
##
5、查询多条数据为map集合
方式一:
/** * 查询所有用户信息为map集合 * @return * 将表中的数据以map集合的方式查询,一条数据对应一个map;若有多条数据,就会产生多个map集合,此 时可以将这些map放在一个list集合中获取 */ List<Map<String, Object>> getAllUserToMap(); <!--Map<String, Object> getAllUserToMap();--> <select id="getAllUserToMap" resultType="map"> select * from t_user </select>
方式二:
/** * 查询所有用户信息为map集合 * @return * 将表中的数据以map集合的方式查询,一条数据对应一个map;若有多条数据,就会产生多个map集合,并 且最终要以一个map的方式返回数据,此时需要通过@MapKey注解设置map集合的键,值是每条数据所对应的 map集合 */ @MapKey("id") Map<String, Object> getAllUserToMap(); <!--Map<String, Object> getAllUserToMap();--> <select id="getAllUserToMap" resultType="map"> select * from t_user </select> 结果: <!-- { 1={password=123456, sex=男, id=1, age=23, username=admin}, 2={password=123456, sex=男, id=2, age=23, username=张三}, 3={password=123456, sex=男, id=3, age=23, username=张三} } -->
总结: MyBatis的各种查询功能: 1、若查询出的数据只有一条
-
可以通过实体类对象接收
-
可以通过List集合接收
-
可以通过map集合接收
-
-
结果:{password=123456, sex=男, id=4, age=20, email=123@qq.com, username=张三
-
-
若查询出的数据有多条
-
-
可以通过List集合接收
-
可以通过map类型的list集合接收
-
-
-
可以在mapper接口的方法添加@MapKey注解,此时就可以将每条数据转换的map集合作为值,以某个字段为键,放在同一个map集合中。
-
注意:一定不能通过实体类对象接收,此时会抛出异常TooManyResultException
Mybatis中设置了默认的类型别名
java.lang.Integer --> int, integer
int --> _int, _integer
Map --> map
String --> string
测试类
package com.atguigu.mybatis.test; import com.atguigu.mybatis.mapper.SelectMapper; import com.atguigu.mybatis.pojo.User; import com.atguigu.mybatis.utils.SqlSessionUtils; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import java.util.List; import java.util.Map; /** * * date: 2022/02/28 * Description: */ public class SelectMapperTest { /** * MyBatis的各种查询功能: * 1、若查询出的数据只有一条 * a>可以通过实体类对象接收 * b>可以通过List集合接收 * c>可以通过map集合接收 * 结果:{password=123456, sex=男, id=4, age=20, email=123@qq.com, username=张三} * 2、若查询出的数据有多条 * a>可以通过List集合接收 * b>可以通过map类型的list集合接收 * c>可以在mapper接口的方法添加@MapKey注解,此时就可以将每条数据转换的map集合作为值, * 以某个字段的值作为键,放在同一个map集合中。 * 注意:一定不能通过实体类对象接收,此时会抛出异常TooManyResultException * * Mybatis中设置了默认的类型别名 * java.lang.Integer --> int, integer * int-->_int,_integer * Map-->map * String-->string */ @Test public void testGetAllUserToMap() { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); SelectMapper mapper = sqlSession.getMapper(SelectMapper.class); Map<String, Object> userByIdToMap = mapper.getAllUserToMap(); System.out.println(userByIdToMap); sqlSession.close(); } @Test public void testGetUserByIdToMap() { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); SelectMapper mapper = sqlSession.getMapper(SelectMapper.class); Map<String, Object> userByIdToMap = mapper.getUserByIdToMap(4); System.out.println(userByIdToMap); sqlSession.close(); } @Test public void testGetCount() { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); SelectMapper mapper = sqlSession.getMapper(SelectMapper.class); Integer count = mapper.getCount(); System.out.println("count = " + count); sqlSession.close(); } @Test public void testGetAllUser() { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); SelectMapper mapper = sqlSession.getMapper(SelectMapper.class); List<User> users = mapper.getAllUsers(); users.forEach(user -> System.out.println(user)); sqlSession.close(); } @Test public void testGetUserById() { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); SelectMapper mapper = sqlSession.getMapper(SelectMapper.class); System.out.println(mapper.getUserById(4)); sqlSession.close(); } }
七、特殊SQL的执行
1、模糊查询
三种方式
-
'%{xxx}%'
-
concat('%', #{xxx}, '%')
-
"%"#{xxxx}"%" 用的最多
/** * 根据用户模糊查询用户信息 */ List<User> getUserByLike(@Param("username") String username); <!--List<User> getUserByLike(@Param("username") String username);--> <select id="getUserByLike" resultType="User"> <!--select * from t_user where username like '%${username}%' --> <!-- select * from t_user where username like concat('%', #{username}, '%') --> select * from t_user where username like "%"#{username}"%" </select>
2、批量删除
/** * 批量删除 */ int deleteMore(@Param("ids") String ids); <!--int deleteMore(@Param("ids") String ids);--> <delete id="deleteMore"> delete from t_user where id in(${ids}) </delete>
-
这里使用 ${} 的原因是:解析后不带 ''
3、动态设置表名
/** * 查询指定表中的数据 */ List<User> getUserByTableName(@Param("tableName") String tableName); <!--List<User> getUserByTableName(@Param("tableName") String tableName);--> <select id="getUserByTableName" resultType="User"> select * from ${tableName} </select>
4、添加功能获取自增的主键
-
userGeneratedKeys:设置使用自增的主键
-
KeyProperty:因为增删改查有统一的返回值是受影响的行数,因此只能将获取的自增的主键放在传输的参数user对象的某个属性中
/** * 添加用户信息 */ void insertUser(User user); <!-- void insertUser(User user); useGeneratedKeys:设置当前标签的sql使用了自增的主键 keyProperty:将自增的主键赋值给传输到映射文件中参数的某个属性 --> <insert id="insertUser" useGeneratedKeys="true" keyProperty="id"> insert into t_user values(null, #{username}, #{password}, #{age}, #{sex}, #{email}) </insert>
测试类:
public class SQLMapperTest { @Test public void testGetUserByLike() { SqlSession session = SqlSessionUtils.getSqlSession(); SQLMapper mapper = session.getMapper(SQLMapper.class); List<User> list = mapper.getUserByLike("a"); System.out.println(list); } @Test public void testDeleteMore() { SqlSession session = SqlSessionUtils.getSqlSession(); SQLMapper mapper = session.getMapper(SQLMapper.class); int result = mapper.deleteMore("2,3,4"); System.out.println(result); } @Test public void testGetUserByTableName() { SqlSession session = SqlSessionUtils.getSqlSession(); SQLMapper mapper = session.getMapper(SQLMapper.class); List<User> list = mapper.getUserByTableName("t_user"); System.out.println(list); } @Test public void testJDBC() throws Exception { Class.forName(""); Connection connection = DriverManager.getConnection("", "", ""); PreparedStatement ps = connection.prepareStatement("insert", Statement.RETURN_GENERATED_KEYS); ps.executeUpdate(); ResultSet resultSet = ps.getGeneratedKeys(); } @Test public void testInsertUser() { SqlSession session = SqlSessionUtils.getSqlSession(); SQLMapper mapper = session.getMapper(SQLMapper.class); User user = new User(null, "王五", "123", 23, "男", "wangwu@qq.com"); mapper.insertUser(user); System.out.println(user); } }
八、自定义映射resultMap
1、resultMap处理字段和属性的映射的关系
若字段名和实体列中的属性名不一致,则可以通过resultMap设置自定义映射
<!-- resultMap:设置自定义映射关系 id:唯一标识,不能重复 type:设置映射关系中的实体类类型 子标签: id:设置主键的映射关系 result:设置普通字段的映射关系 属性: property:设置映射关系中的属性名,必须是type属性所设置的实体类类型中的属性名 column:设置映射关系中的字符名,必须是sql语句查询出的字段名 --> <resultMap id="empResultMap" type="Emp"> <id property="eid" column="eid" /> <result property="empName" column="emp_name" /> <result property="age" column="age" /> <result property="sex" column="sex" /> <result property="email" column="email" /> </resultMap> <!--List<Emp> getAllEmp();--> <select id="getAllEmp" resultMap="empResultMap"> <!--select eid, emp_name empName, age, sex, email from t_emp--> select * from t_emp </select>
若字段名和实体类中的属性名不一致,但是字段名符合数据库的规则(使用_),实体类中的属性 名符合Java的规则(使用驼峰)
此时也可通过以下两种方式处理字段名和实体类中的属性的映射关系 a>可以通过为字段起别名的方式,保证和实体类中的属性名保持一致
b>可以在MyBatis的核心配置文件中设置一个全局配置信息mapUnderscoreToCamelCase,可 以在查询表中数据时,自动将_类型的字段名转换为驼峰
例如:字段名user_name,设置了mapUnderscoreToCamelCase,此时字段名就会转换为 userName
2、多对一映射处理
查询员工信息以及员工所对应的部门信息
a>级联方式处理映射关系
/** * 查询员工股以及员工对应的部门信息 */ Emp getEmpAndDept(@Param("eid") Integer eid); <!--处理多对一的映射关系方式一:级联属性赋值--> <resultMap id="empAndDeptResultMapOne" type="Emp"> <id property="eid" column="eid" /> <result property="empName" column="emp_name" /> <result property="age" column="age" /> <result property="sex" column="sex" /> <result property="dept.did" column="did" /> <result property="dept.deptName" column="dept_name" /> </resultMap> <!--Emp getEmpAndDept(@Param("eid") Integer eid);--> <select id="getEmpAndDept" resultMap="empAndDeptResultMapOne"> select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{eid} </select>
b>使用assocation处理映射关系
<!--处理多对一映射关系方式二:association--> <resultMap id="empAndDeptResultMapTwo" type="Emp"> <id property="eid" column="eid" /> <result property="empName" column="emp_name" /> <result property="age" column="age" /> <result property="sex" column="sex" /> <result property="email" column="email" /> <!-- association:处理多对一的映射关系 property:需要处理的属性名 javaType:该属性的类型 --> <association property="dept" javaType="Dept"> <id property="did" column="did" /> <result property="deptName" column="dept_name" /> </association> </resultMap> <!--Emp getEmpAndDept(@Param("eid") Integer eid);--> <select id="getEmpAndDept" resultMap="empAndDeptResultMapTwo"> select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{eid} </select>
c>分布查询
-
查询员工信息
/** * 通过分布查询查询员工以及员工所对应的部门信息 * 分布查询第一步:查询员工信息 */ Emp getEmpAndDeptByStepOne(@Param("eid") Integer eid);
-
根据员工所对应的部门di查询部门信息
/** * 通过分布查询员工以及员工所对应的部门信息 * 分布查询第二步:通过did查询员工对应的部门 */ Dept getEmpAndDeptByStepTwo(@Param("did")Integer did); <resultMap id="empAndDeptByStepResultMap" type="Emp"> <id property="eid" column="eid" /> <result property="empName" column="emp_name" /> <result property="age" column="age" /> <result property="sex" column="sex" /> <result property="email" column="email" /> <!-- select:设置分布查询的sql的唯一标识(namespace.SQLId或mapper接口的全类名.方法名) column:设置分布查询的条件 fetchType:当开启了全局的延迟加载之后,可通过此属性手动控制延迟加载的效果 fetchType:"lazy|eager":lazy表示延迟加载,eager表示立即加载 --> <association property="dept" select="com.atguigu.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo" column="did" fetchType="eager" /> </resultMap> <!--Dept getEmpAndDeptByStepOne(@Param("eid") Integer eid);--> <select id="getEmpAndDeptByStepOne" resultMap="empAndDeptByStepResultMap" > select * from t_emp where eid = #{eid} </select> <!--Dept getEmpAndDeptByStepTwo(@Param("did")Integer did);--> <select id="getEmpAndDeptByStepTwo" resultType="Dept"> select * from t_dept where did = #{did} </select>
3、一对多映射处理
a>collection
/** * 获取部门以及部门中所有员工信息 * 一对多的关系 */ Dept getDeptAndEmps(@Param("did") Integer did); <resultMap id="deptAndEmpsResultMap" type="Dept"> <id property="did" column="did" /> <result property="deptName" column="dept_name" /> <!-- collection:处理一对多的映射关系 ofType:表示该属性所对应的集合中存储数据的类型 --> <collection property="emps" ofType="Emp"> <id property="eid" column="eid" /> <result property="empName" column="emp_name" /> <result property="age" column="age" /> <result property="sex" column="sex" /> <result property="email" column="email" /> </collection> </resultMap> <!--Dept getDeptAndEmps(@Param("did") Integer did);--> <select id="getDeptAndEmps" resultMap="deptAndEmpsResultMap"> select * from t_dept left join t_emp on t_dept.did = t_emp.did where t_dept.did = #{did} </select>
b>分布查询
-
查询部门信息
/** * 通过分布查询部门以及部门中所有的员工信息 * 分布查询第一步:查询部门信息 */ Dept getDeptAndEmpByStepOne(@Param("did")Integer did);
-
根据部门id查询部门中所有的员工
/** * 通过分布查询部门以及部门中所有的员工信息系 * 分布查询第二步:根据did查询员工信息 */ List<Emp> getDeptAndEmpByStepTwo(@Param("did") Integer did);
<resultMap id="deptAndEmpByStepResultMap" type="Dept"> <id property="did" column="did" /> <result property="deptName" column="dept_name" /> <collection property="emps" select="com.atguigu.mybatis.mapper.EmpMapper.getDeptAndEmpByStepTwo" fetchType="eager" column="did"/> </resultMap> <!--Dept getDeptAndEmpByStepOne(@Param("did")Integer did);--> <select id="getDeptAndEmpByStepOne" resultMap="deptAndEmpByStepResultMap"> select * from t_dept where did = #{did} </select> <!--List<Emp> getDeptAndEmpByStepTwo(@Param("did") Integer did)--> <select id="getDeptAndEmpByStepTwo" resultType="Emp"> select * from t_emp where did = #{did} </select>
分布查询的优点:可以实现延迟加载,但是必须在核心配置文件中设置全局配置信息
-
lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载
-
aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个 属性会按需加载
-
此时就可以实现按需加载,获取的数据是什么,就只会执行相应的sql。此时可通过association和 collection中的fetchType属性设置当前的分步查询是否使用延迟加载,fetchType="lazy(延迟加 载)|eager(立即加载)"
* *解决字段名和属性名不一致的情况: * a>为字段起别名,保持和属性名的一致 * b>设置全局配置,将_**自动映射为驼峰
* c>通过resultMap设置自定义的映射关系*
测试类:
package com.atguigu.mybatis.test; import com.atguigu.mybatis.mapper.DeptMapper; import com.atguigu.mybatis.mapper.EmpMapper; import com.atguigu.mybatis.pojo.Dept; import com.atguigu.mybatis.pojo.Emp; import com.atguigu.mybatis.utils.SqlSessionUtils; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import java.util.List; /** * * date: 2022/03/01 * Description: */ public class ResultMapTest { /** * 解决字段名和属性名不一致的情况: * a>为字段起别名,保持和属性名的一致 * b>设置全局配置,将_自动映射为驼峰 * <setting name="mapUnderscoreToCamelCase" value="true"/> * c>通过resultMap设置自定义的映射关系 * * <resultMap id="empResultMap" type="Emp"> * <id property="eid" column="eid" /> * <result property="empName" column="emp_name" /> * <result property="age" column="age" /> * <result property="sex" column="sex" /> * <result property="email" column="email" /> * </resultMap> * * 处理多对一的映射关系: * a>级联属性赋值 * b>association * * 处理一对多的映射关系 * a>collection * b>分布查询 */ @Test public void testGetAllEmp() { SqlSession session = SqlSessionUtils.getSqlSession(); EmpMapper mapper = session.getMapper(EmpMapper.class); List<Emp> list = mapper.getAllEmp(); list.forEach(emp -> System.out.println(emp)); } @Test public void testGetEmpAndDept() { SqlSession session = SqlSessionUtils.getSqlSession(); EmpMapper mapper = session.getMapper(EmpMapper.class); Emp emp = mapper.getEmpAndDept(2); System.out.println(emp); } @Test public void testGetEmpAndDeptByStep() { SqlSession session = SqlSessionUtils.getSqlSession(); EmpMapper mapper = session.getMapper(EmpMapper.class); Emp emp = mapper.getEmpAndDeptByStepOne(3); System.out.println(emp.getEmpName()); System.out.println("++++++++++++++++++++++++"); System.out.println(emp.getDept()); } @Test public void testGetDeptAndEmps() { SqlSession session = SqlSessionUtils.getSqlSession(); DeptMapper mapper = session.getMapper(DeptMapper.class); Dept dept = mapper.getDeptAndEmps(1); System.out.println("dept = " + dept); /* 运行结果: dept = Dept(did=1, deptName=A, emps=[ Emp(eid=1, empName=张三, age=23, sex=男, email=123@qq.com, dept=null), Emp(eid=4, empName=赵六, age=34, sex=男, email=123@qq.com, dept=null) ]) */ } @Test public void testGetDeptAndEmpByStep() { SqlSession session = SqlSessionUtils.getSqlSession(); DeptMapper mapper = session.getMapper(DeptMapper.class); Dept dept = mapper.getDeptAndEmpByStepOne(1); System.out.println(dept.getDeptName()); } }
九、动态SQL
Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了解决
拼接SQL语句字符串时的痛点问题。
1、if
if标签可通过test属性的表达式进行判断,若表达式的结果为true,则标签中的内容会执行;反之标签中 的内容不会执行
/** * 多条件查询 */ List<Emp> getEmpByCondition(Emp emp); <!-- List<Emp> getEmpByCondition(Emp emp);--> <select id="getEmpByCondition" resultType="Emp"> select * from t_emp where 1=1 <if test="empName != null and empName != ''"> emp_name = #{empName} </if> <if test="age != null and age != ''"> and age = #{age} </if> <if test="sex != null and sex != ''"> and sex = #{sex} </if> <if test="email != null and email != ''"> and email = #{email} </if> </select>
2、where
<!--where标签--> <select id="getEmpByCondition" resultType="Emp"> select * from t_emp <where> <if test="empName != null and empName != ''"> emp_name = #{empName} </if> <if test="age != null and age != ''"> and age = #{age} </if> <if test="sex != null and sex != ''"> and sex = #{sex} </if> <if test="email != null and email != ''"> and email = #{email} </if> </where> </select>
where和if一般结合使用:
a>若where标签中的if条件都不满足,则where标签没有任何功能,即不会添加where关键字
b>若where标签中的if条件满足,则where标签会自动添加where关键字,并将条件最前方多余的 and去掉
注意:where标签不能去掉条件最后多余的and
3、trim
<select id="getEmpByCondition" resultType="Emp"> select * from t_emp <trim prefix="where" suffixOverrides="and|or"> <if test="empName != null and empName != ''"> emp_name = #{empName} and </if> <if test="age != null and age != ''"> age = #{age} or </if> <if test="sex != null and sex != ''"> sex = #{sex} and </if> <if test="email != null and email != ''"> email = #{email} </if> </trim> </select>
trim用于去掉或添加标签中的内容
常用属性:
-
prefix:在trim标签中的内容的前面添加某些内容
-
prefixOverrides:在trim标签中的内容的前面去掉某些内容
-
suffix:在trim标签中的内容的后面添加某些内容
-
suffixOverrides:在trim标签中的内容的后面去掉某些内容
4、choose、when、otherwise
choose、when、otherwise相当于if...else if..else
/** * 测试choose、when、otherwise */ List<Emp> getEmpByChoose(Emp emp); <!--List<Emp> getEmpByChoose(Emp emp);--> <select id="getEmpByChoose" resultType="Emp"> select * from t_emp <where> <choose> <when test="empName != null and empName != ''"> emp_name = #{empName} </when> <when test="age != null and age != ''"> age = #{age} </when> <when test="sex != null and sex != ''"> sex = #{sex} </when> <when test="email != null and email != ''"> email = #{email} </when> <otherwise> did = 1 </otherwise> </choose> </where> </select>
5、foreach
/** * 通过数组实现批量删除 */ int deleteMoreByArray(@Param("eids") Integer[] eids); /** * 通过List集合实现批量添加 */ int insertMoreByList(@Param("emps") List<Emp> emps); <!--int deleteMoreByArray(@Param("eids") Integer[] eids);--> <delete id="deleteMoreByArray"> <!--delete from t_emp where eid in--> <!-- <foreach collection="eids" item="eid" open="(" close=")" separator=","> #{eid} </foreach> --> delete from t_emp where eid = <foreach collection="eids" item="eid" separator="or"> #{eid} </foreach> </delete> <!--int insertMoreByList(List<Emp> emps);--> <insert id="insertMoreByList"> insert into t_emp values <foreach collection="emps" item="emp" separator=","> (null,#{emp.empName},#{emp.age},#{emp.sex},#{emp.email},null) </foreach> </insert>
属性:
-
collection:设置要循环的数组或集合
-
item:表示集合或数组中的每一个数据
-
separator:设置循环体之间的分隔符
-
open:设置foreach标签中的内容的开始符
-
close:设置foreach标签中的内容的结束符
6、SQL片段
sql片段,可以记录一段公共sql片段,在使用的地方通过include标签进行引入
<sql id="empColumns">eid,emp_name,age,sex,email</sql> 引入公共部分 select <include refid="empColumns"></include> from t_emp
package com.atguigu.mybatis.test; import com.atguigu.mybatis.mapper.DynamicSQLMapper; import com.atguigu.mybatis.pojo.Emp; import com.atguigu.mybatis.utils.SqlSessionUtils; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import java.util.Arrays; import java.util.List; /** * * date: 2022/03/01 * Description: */ public class DynamicSQLTest { /** * 动态SQL: * 1、if:根据标签中test属性所对应的表达式决定标签中的内容是否需要拼接到SQL中 * 2、where: * 当where标签中有内容时,会自动生成where关键字,并且将内容多余的and或or去掉 * 当where标签中没有内容时,此时where标签没有任何效果 * 注意:where标签不能将其中内容后面多余的and或or去掉 * 3、trim * 若标签有内容时: * prefix|suffix:将trim标签中内容前面或后面添加指定内容 * suffixOverrides|prefixOverrides:将trim标签中内容前面或后面去掉指定内容 * 若标签中没有内容时,trim标签也没有任何效果 * 4、choose、when、otherwise,相当于if...else if ... else * when至少要有一个,otherwise最多只能有一个 * 5、foreach * collection:设置需要寻相互的数组或集合 * item:表示数组或集合中的每一个数据 * separator:循环体之间的分隔符 * open:foreach标签所循环的所有内容的开始符 * close:foreach标签所循环的所有内容的结果符 * 6、sql标签 * */ @Test public void testInsertMoreByList() { SqlSession session = SqlSessionUtils.getSqlSession(); DynamicSQLMapper mapper = session.getMapper(DynamicSQLMapper.class); Emp emp1 = new Emp(null, "a1", 23, "男", "123@qq.com"); Emp emp2 = new Emp(null, "a2", 23, "男", "123@qq.com"); Emp emp3 = new Emp(null, "a3", 23, "男", "123@qq.com"); List<Emp> emps = Arrays.asList(emp1, emp2, emp3); int rows = mapper.insertMoreByList(emps); System.out.println(rows); } @Test public void testDeleteMoreByArray() { SqlSession session = SqlSessionUtils.getSqlSession(); DynamicSQLMapper mapper = session.getMapper(DynamicSQLMapper.class); int rows = mapper.deleteMoreByArray(new Integer[]{6, 7, 8}); System.out.println(rows); } @Test public void testGetEmpByChoose() { SqlSession session = SqlSessionUtils.getSqlSession(); DynamicSQLMapper mapper = session.getMapper(DynamicSQLMapper.class); List<Emp> list = mapper.getEmpByChoose(new Emp(null, "张三", null, "", "")); System.out.println(list); } @Test public void testGetEmpByCondition() { SqlSession session = SqlSessionUtils.getSqlSession(); DynamicSQLMapper mapper = session.getMapper(DynamicSQLMapper.class); List<Emp> list = mapper.getEmpByCondition(new Emp(null, "", null, "", "")); System.out.println(list); } }
十、MyBatis的缓存
1、MyBatis的一级缓存
一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就 会从缓存中直接获取,不会从数据库重新访问
使一级缓存失效的四种情况:
-
不同的SqlSession对应不同的一级缓存
-
同一个SqlSession但是查询条件不同
-
同一个SqlSession两次查询期间执行了任何一次增删改操作
-
同一个SqlSession两次查询期间手动清空了缓存
2、MyBatis的二级缓存
二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被
缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取
二级缓存开启的条件:
-
在核心配置文件中,设置全局配置属性cacheEnabled="true",默认为true,不需要设置
-
在映射文件中设置标签<cache />
-
二级缓存必须在SqlSession关闭或提交之后有效
-
查询的数据所转换的实体类类型必须实现序列化的接口 (Serializable)
使二级缓存失效的情况:
两次查询之间执行了任意的增删改,会使一级和二级缓存同时失**效**
3、二级缓存的相关配置
在mapper配置文件中添加的cache标签可以设置一些属性:
-
eviction属性:缓存回收策略
LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。
FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
默认的是 LRU。
-
flushInterval属性:刷新间隔,单位毫秒
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
-
size属性:引用数目,正整数
代表缓存最多可以存储多少个对象,太大容易导致内存溢出
-
readOnly属性:只读,true/false
true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了 很重要的性能优势。
false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。
4、MyBatis缓存查询的顺序
-
先查询二级缓存,因为耳机缓存中可能会有其它程序已经查出来数据,可以哪来直接使用
-
如果二级缓存没有命中,再查询一级缓存
-
如果一级缓存也没有命中吗,则查询数据库
-
SqlSession关闭之后,一级缓存中数据会写入二级缓存
5、整合第三份缓存EHCache
a>添加依赖
<!-- Mybatis EHCache整合包 --> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.2.1</version> </dependency> <!-- slf4j日志门面的一个具体实现 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>
b>各jar包功能
c>创建EHCache的配置文件ehache.xml
<?xml version="1.0" encoding="utf-8" ?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <!-- 磁盘保存路径 --> <diskStore path="D:\atguigu\ehcache"/> <defaultCache maxElementsInMemory="1000" maxElementsOnDisk="10000000" eternal="false" overflowToDisk="true" timeToIdleSeconds="120" timeToLiveSeconds="120" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> </ehcache>
d> 设置二级缓存的类型
<cache type="org.mybatis.caches.ehcache.EhcacheCache" />
e>加入logback日志
存在SLF4J时,作为简易日志的log4j将失效,此时我们需要借助SLF4J的具体实现logback来打印日志。
创建logback的配置文件logback.xml
<?xml version="1.0" encoding="UTF-8"?> <configuration debug="true"> <!-- 指定日志输出的位置 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <!-- 日志输出的格式 --> <!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 --> <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n </pattern> </encoder> </appender> <!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR --> <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 --> <root level="DEBUG"> <!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender --> <appender-ref ref="STDOUT"/> </root> <!-- 根据特殊需求指定局部日志级别 --> <logger name="com.atguigu.crowd.mapper" level="DEBUG"/> </configuration>
f>EHCache配置文件说明
package com.atguigu.mybatis.test; import com.atguigu.mybatis.mapper.CacheMapper; import com.atguigu.mybatis.pojo.Emp; import com.atguigu.mybatis.utils.SqlSessionUtils; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Test; import java.io.IOException; import java.io.InputStream; /** * * date: 2022/03/01 * Description: 缓存 --> 提高我们的查询效率的 */ public class CacheMapperTest { @Test public void testOneCache() { SqlSession sqlSession1 = SqlSessionUtils.getSqlSession(); CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class); Emp emp1 = mapper1.getEmpByEid(1); System.out.println(emp1); // 3.在两次查询之间执行了增删改查的操作 // mapper1.insertEmp(new Emp(null, "abc", 23, "男", "123@qq.com")); // 4.手动清空的缓存 sqlSession1.clearCache(); Emp emp2 = mapper1.getEmpByEid(1); System.out.println(emp2); /*SqlSession sqlSession2 = SqlSessionUtils.getSqlSession(); CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class); Emp emp2 = mapper2.getEmpByEid(1); System.out.println(emp2);*/ } @Test public void testTwoCache() { try { InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is); SqlSession sqlSession1 = factory.openSession(true); CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class); System.out.println(mapper1.getEmpByEid(1)); SqlSession sqlSession2 = factory.openSession(true); CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class); System.out.println(mapper2.getEmpByEid(1)); } catch (IOException e) { e.printStackTrace(); } } }
十一、MyBatis的逆向工程
1、创建逆向工程和插件
-
正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate是支持正向工程
的。
-
逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:
-
-
Java实体类
-
Mapper接口
-
-
-
Mapper映射文件
-
a>添加依赖和插件
<!-- 依赖MyBatis核心包 --> <dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.27</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.22</version> </dependency> </dependencies> <!-- 控制Maven在构建过程中相关配置 --> <build> <!-- 构建过程中用到的插件 --> <plugins> <!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 --> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.0</version> <!-- 插件的依赖 --> <dependencies> <!-- 逆向工程的核心依赖 --> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.3.2</version> </dependency> <!-- 数据库连接池 --> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.2</version> </dependency> <!-- MySQL驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.27</version> </dependency> </dependencies> </plugin> </plugins> </build>
b>创建MyBatis的核心配置文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!--引入properties配置文件--> <properties resource="jdbc.properties"/> <typeAliases> <package name="com.atguigu.mybatis.pojo"/> </typeAliases> <plugins> <!--设置分页插件--> <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin> </plugins> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <!--driver:驱动内容--> <property name="driver" value="${jdbc.driver}"/> <!--连接数据库的url--> <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.atguigu.mybatis.mapper"/> </mappers> </configuration>
c>创建逆向工程的配置文件
文件名必须是:generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <!-- targetRuntime: 执行生成的逆向工程的版本 MyBatis3Simple: 生成基本的CRUD(清新简洁版) MyBatis3: 生成带条件的CRUD(奢华尊享版) --> <!--<context id="DB2Tables" targetRuntime="MyBatis3Simple">--> <context id="DB2Tables" targetRuntime="MyBatis3"> <!-- 数据库的连接信息 --> <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/mybatis" userId="root" password="root"> </jdbcConnection> <!-- javaBean的生成策略--> <javaModelGenerator targetPackage="com.atguigu.mybatis.pojo" targetProject="./src/main/java"> <!--是否能够使用子包--> <property name="enableSubPackages" value="true"/> <!--去掉字符串的前后空格--> <property name="trimStrings" value="true"/> </javaModelGenerator> <!-- SQL映射文件的生成策略 --> <sqlMapGenerator targetPackage="com.atguigu.mybatis.mapper" targetProject="./src/main/resources"> <property name="enableSubPackages" value="true"/> </sqlMapGenerator> <!-- Mapper接口的生成策略 --> <javaClientGenerator type="XMLMAPPER" targetPackage="com.atguigu.mybatis.mapper" targetProject="./src/main/java"> <property name="enableSubPackages" value="true"/> </javaClientGenerator> <!-- 逆向分析的表 --> <!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName --> <!-- domainObjectName属性指定生成出来的实体类的类名 --> <table tableName="t_emp" domainObjectName="Emp"/> <table tableName="t_dept" domainObjectName="Dept"/> </context> </generatorConfiguration>
这里mac和linux的路径符号为:/
Windows复制须改为:\
b>执行MBG插件的generate目标
2、QBC查询
@Test public void testMBG() { try { InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is); SqlSession sqlSession = factory.openSession(true); EmpMapper mapper = sqlSession.getMapper(EmpMapper.class); // List<Emp> emps = mapper.selectByExample(null); // 根据条件查找 没有条件就是查询所有 // emps.forEach(System.out::println); // 根据条件查询 /*EmpExample example = new EmpExample(); example.createCriteria().andEmpNameEqualTo("张三").andAgeGreaterThanOrEqualTo(20); // 创建一个条件 查询相当于 emp_name = "张三"的 example.or().andDidIsNotNull(); // 或者 List<Emp> list = mapper.selectByExample(example); list.forEach(System.out::println);*/ // 修改 // mapper.updateByPrimaryKey(new Emp(1, "admin", 22, null, "456@qq.com", 3)); // 当传入的属性值为null时,是不会修改对应的字段值的(选择性修改) mapper.updateByPrimaryKeySelective(new Emp(1, "admin", 22, null, "456@qq.com", 3)); } catch (IOException e) { e.printStackTrace(); } }
十二、分页插件
1、分页插件使用步骤
a>添加依赖
<!--添加分页依赖--> <!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.2.0</version> </dependency>
b>配置分页插件
在MyBatis的核心配置文件中配置插件
<plugins> <!--设置分页插件--> <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin> </plugins>
2、分页插件的使用
a>在查询功能之前使用PageHelper.startPage(int pageNum, int pageSize)开启分页功能
pageNum:当前页的页码
pageSize:每页显示的条数
b>在查询获取list集合之后,使用PageInfo<T> pageInfo = new PageInfo<>(List<T> list, int navigatePages)获取分页相关数据
list:分页之后的数据
navigatePages:导航分页的页码数
c>分页相关数据
PageInfo{
pageNum=8, pageSize=4, size=2, startRow=29, endRow=30, total=30, pages=8,
list=Page{count=true, pageNum=8, pageSize=4, startRow=28, endRow=32, total=30, pages=8, reasonable=false, pageSizeZero=false},
prePage=7, nextPage=0, isFirstPage=false, isLastPage=true, hasPreviousPage=true, hasNextPage=false, navigatePages=5, navigateFirstPage4, navigateLastPage8, navigatepageNums=[4, 5, 6, 7, 8]
}
常用数据: pageNum:当前页的页码 pageSize:每页显示的条数 size:当前页显示的真实条数 total:总记录数 pages:总页数 prePage:上一页的页码 nextPage:下一页的页码
isFirstPage/isLastPage:是否为第一页/最后一页 hasPreviousPage/hasNextPage:是否存在上一页/下一页 navigatePages:导航分页的页码数 navigatepageNums:导航分页的页码,[1,2,3,4,5]
/** * date: 2022/03/01 * Description: */ public class PageHelperTest { /** * limit index,pageSize * index:当前页的起始索引 * pageSize:每页显示的条数 * pageNum:当前页的页码 * * index=(pageNum-1)*pageSize * * 使用MyBatis的分页插件实现分页功能: * 1、需要在查询功能之前开启跟叶 * PageHelper.startPage(int pageNum, int pageSize); * 2、在查询功能之后获取分页相关信息 * PageInfo<Emp> pageInfo = new PageInfo(list, 5); * list表示分页数据 * 5表示当前导航分页的数量 */ @Test public void testMBG() { try { InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is); SqlSession sqlSession = factory.openSession(true); EmpMapper mapper = sqlSession.getMapper(EmpMapper.class); // Page<Object> page = PageHelper.startPage(1, 5); List<Emp> list = mapper.selectByExample(null); // list.forEach(System.out::println); // System.out.println(page); PageInfo<Emp> pageInfo = new PageInfo<>(list, 5); // 导航分页使用 System.out.println("pageInfo = " + pageInfo); } catch (IOException e) { e.printStackTrace(); } } }