框架概述
框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种定义认为,框架是可被应用开发者定制的应用骨架。前者是从应用方面而后者是从目的方面给出的定义。
简而言之,框架是软件(系统)的半成品,框架封装了很多的细节,使开发者可以使用简单的方式实现功能,大大提高开发效率。
一句话解释: 框架其实就是一套模板,或者可重用的设计,套路。按照这种套路来写代码。尤其是在开发大型项目上,框架的作用体现得就更加淋漓尽致。小型项目,没有框架的用武之地。!
框架要解决的问题
框架要解决的最重要的一个问题是技术整合的问题,在 JAVA EE 的 框架中,有着各种各样的技术,不同的软件企业需要从J2EE 中选择不同的技术,这就使得软件企业最终的应用依赖于这些技术,技术自身的复杂性和技术的风险性将会直接对应用造成冲击。而应用是软件企业的核心,是竞争力的关键所在,因此应该将应用自身的设计和具体的实现技术解耦。这样,软件企业的研发将集中在应用的设计上,而不是具体的技术实现,技术实现是应用的底层支撑,它不应该直接对应用产生影响。
框架一般处在低层应用平台(如 J2EE)和高层业务逻辑之间的中间层。
代码是要分层,项目要分层,每一层的代码需要进行交互,对话。
Log4j日志
我们在使用MyBatis的时候, 其实MyBatis框架会打印一些必要的日志信息, 在开发阶段这些日志信息对我们分析问题,理解代码的执行是特别有帮助的; 包括项目上线之后,我们也可以收集项目的错误日志到文件里面去; 所以我们采用专门的日志系统来处理.
步骤
引入依赖
<!-- log start --> <!-- 日志的具体实现 --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency> <!-- 接口 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.6.6</version> </dependency> <!-- 中间的转化jar包 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.6</version> </dependency>
在resources文件夹下,创建一个文件 log4j.properties 这个名字是固定的!
##设置日志记录到控制台的方式 log4j.appender.std=org.apache.log4j.ConsoleAppender log4j.appender.std.Target=System.err log4j.appender.std.layout=org.apache.log4j.PatternLayout log4j.appender.std.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %5p %c{1}:%L - %m%n ##设置日志记录到文件的方式 log4j.appender.file=org.apache.log4j.FileAppender log4j.appender.file.File=mylog.txt log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n ##日志输出的级别,以及配置记录方案 log4j.rootLogger= debug,std,file ## 级别:error > warn > info>debug>trace
MyBatis
mybatis 是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。
mybatis 通过xml 或注解的方式将执行的各种statement 配置起来,并通过java 对象和statement 中sql的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql并将结果映射为 java 对象并返回。采用 ORM ( Object relational mapping)思想解决了实体和数据库映射的问题,对jdbc 进行了封装,屏蔽了jdbc api 底层访问细节,使我们不用与 jdbc api打交道,就可以完成对数据库的访问操作。
核心配置文件
在resources 下面创建 SqlMapConfig.xml 配置文件 <!--核心配置文件-->
核心配置文件中属性的顺序
属性 说明 properties 引入外部properties文件 settings 全局配置参数 typeAliases 类型别名 typeHandlers 类型处理器 objectFactory 对象工厂 plugins 插件 environments 环境集合属性对象 mappers 映射器 新建外部jdbc.properties配置文件
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mybatis_day01?characterEncoding=utf-8 jdbc.user=root jdbc.password=root
核心配置文件
<?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="db.properties"/> <!-- 开启redis二级缓存 默认就是开启的可以不进行设置--> <settings> <setting name="cacheEnabled" value = "true"/> </settings> <!-- 给我们自己的类起别名--> <typeAliases> <!-- 给指定的类起别名 type: 具体的类 alias : 起的别名 --> <!--<typeAlias type="com.zml.pojo.User" alias="user"/>--> <!-- 给指定的类起别名 type: 具体的类 ,它的别名就是它的类名,大小写无所谓。起了别名的类则可以直接使用别名而无需再写全路径名 --> <typeAlias type="com.zml.pojo.User"/> <!--给这个包下的所有类起别名,它的别名就是它的类名,大小写无所谓。--> <package name="com.zml.pojo"/> </typeAliases> <!-- mybatis 分页插件 --> <plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"> </plugin> </plugins> <!-- 告诉mybatis去连哪个数据库,使用什么账号和密码去连。--> <environments default="outer"> <!--定义一个环境 用来定义数据库在哪里? 账号和密码 可以定义很多的环境 --> <environment id="development"> <!--配置事务,MyBatis事务用的是jdbc ,采用jdbc那一套事务,--> <transactionManager type="JDBC"/> <!--配置连接池, POOLED:使用连接池(mybatis内置的); UNPOOLED:不使用连接池--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis_day01"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> <environment id="outer"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.user}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <!-- 还要告诉mybatis,我们有哪些映射文件--> <mappers> <!-- 引入映射文件路径 --> <mapper resource="com/zml/dao/UserDao.xml"/> <!-- 配置单个接口 --> <mapper class="com.zml.dao.UserDao"></mapper> <!-- 批量配置 --> <package name="com.zml.dao"></package> </mappers> </configuration>
映射文件
<!--采用别名的方式--> <select id="findByUid02" parameterType="int" resultType="user"> select * from t_user where uid = #{uid} </select>
Mybatis 连接池与事务
在 Mybatis 的 SqlMapConfig.xml 配置文件中, 通过
<dataSource type=”pooled”>
来实现 Mybatis 中连接池的配置.
UNPOOLED 不使用连接池的数据源
POOLED 使用连接池的数据源
JNDI 使用 JNDI 实现的数据源,不一样的服务器获得的DataSource是不一样的. 注意: 只有是web项目或者Maven的war工程, 才能使用. 我们用的是tomcat, 用的连接池是dbcp.
MyBatis 在初始化时,解析此文件,根据<dataSource>的 type 属性来创建相应类型的的数据源DataSource,即:
type=”POOLED”: MyBatis 会创建 PooledDataSource 实例, 使用连接池 type=”UNPOOLED” : MyBatis 会创建 UnpooledDataSource 实例, 没有使用的,只有一个连接对象的 type=”JNDI”: MyBatis 会从 JNDI 服务上(tomcat ... jboss...)查找 DataSource 实例,然后返回使用. 只有在web项目里面才有的,用的是服务器里面的. 默认会使用tomcat里面的dbcp
事务的开启<!--sqlSessionFactory.openSession(true)-->
//1. 读取核心配置文件,读取成一个输入流 InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml"); //2. 创建出来sqlsessionfactory的构建器 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //3. 根据构建器,创建sqlsessionfactory、 // 把sqlsession 认识成 连接对象即可 , SqlSessionFactory 就是连接工厂,有点类似连接池! SqlSessionFactory sqlSessionFactory = builder.build(is); //4. 根据sqlsessionfactory , 创建sqlsession SqlSession session = sqlSessionFactory.openSession(true); // 默认是false 关闭自动提交事务,设置为true则开启自动提交事务 //5. 根据sqlsession, 创建UserDao的代理对象 UserDao userDao = session.getMapper(UserDao.class); //6. 调用findAll方法 List<User> list = userDao.findAll(); System.out.println("list=" + list); //7. 关闭sqlsession session.close();
Mybatis入门
创建Maven工程(jar)pom文件中导入坐标
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.zml</groupId> <artifactId>myBatisDemo</artifactId> <version>1.0-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <!--1. 添加依赖--> <dependencies> <!--MyBatis坐标--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency> <!--mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.22</version> </dependency> <!--单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>test</scope> </dependency> <!--lombok 依赖--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.8</version> </dependency> </dependencies> </project>
数据库对应实体对象
package com.zml.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; @Data @NoArgsConstructor @AllArgsConstructor public class User { private int uid; private String username; private String sex; private Date birthday; private String address; }
创建Dao接口
package com.zml.dao; import com.zml.pojo.User; import java.util.List; public interface UserDao { List<User> findAll(); }
在resources 下面创建文件夹 com/zml/dao/UserDao.xml 文件的名字尽量保持与dao包的名字一样。
<?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"> <!-- 1. mapper里面的namespace 一定要写,否则报错: Mapper's namespace cannot be empty namespace一定要写 2. namespace里面一定要Dao接口的全路径名,否则报错: Type interface com.zml.dao.UserDao is not known to the MapperRegistry. UserDao这个接口,在映射器注册中心里面没找到。 --> <mapper namespace="com.zml.dao.UserDao"> <!--2. 定义查询的语句 id : UserDao里面的方法名字 不能写错! resultType: 方法的返回值类型, 如果是一个集合类型,那么里面只需要写 元素的类型即可。 因为如果这个方法查询得到很多条记录回来,mybatis会判定数量的,如果超过一条,它会使用List集合来装 所以我们只要写上集合里面装的类型即可,这也要写全路径名! --> <select id="findAll" resultType="com.zml.pojo.User"> <!-- 3. 写上查询的sql语句 findAll这个方法背后执行的sql语句是什么,要自己写。 --> select * from t_user; </select> </mapper>
在resources 下面创建 SqlMapConfig.xml 配置文件 <!--核心配置文件-->
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <!--配置--> <configuration> <!-- 1. 告诉mybatis去连哪个数据库,使用什么账号和密码去连。--> <environments default="development"> <!--定义一个环境 用来定义数据库在哪里? 账号和密码 可以定义很多的环境 --> <environment id="development"> <!--配置事务,MyBatis事务用的是jdbc ,采用jdbc那一套事务,--> <transactionManager type="JDBC"/> <!--配置连接池, POOLED:使用连接池(mybatis内置的); UNPOOLED:不使用连接池--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis_day01"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <!--2. 还要告诉mybatis,我们有哪些映射文件--> <mappers> <mapper resource="com/zml/dao/UserDao.xml"/> </mappers> </configuration>
定义测试类用来测试
package com.zml.test; import com.zml.dao.UserDao; import com.zml.pojo.User; 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.InputStream; import java.util.List; public class TestUserDao { //测试UserDao的findAll方法 @Test public void testFindAll() throws Exception { //1. 读取核心配置文件,读取成一个输入流 InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml"); //2. 创建出来sqlsessionfactory的构建器 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //3. 根据构建器,创建sqlsessionfactory、 // 把sqlsession 认识成 连接对象即可 , SqlSessionFactory 就是连接工厂,有点类似连接池! SqlSessionFactory sqlSessionFactory = builder.build(is); //4. 根据sqlsessionfactory , 创建sqlsession SqlSession session = sqlSessionFactory.openSession(); //5. 根据sqlsession, 创建UserDao的代理对象 UserDao userDao = session.getMapper(UserDao.class); //6. 调用findAll方法 List<User> list = userDao.findAll(); System.out.println("list=" + list); //7. 关闭sqlsession session.close(); } }
Mybatis完成CRUD
SqlSessionFactory工具类的抽取
/**
* 专门用于返回sqlsession
*/
public class SqlSessionFactoryUtil {
private static SqlSessionFactory factory;
//1. 构建一个sqlsessionfactory
static{
try {
//1.1 读取配置文件
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
//1.2 构建构建器
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//1.3 构建工厂
factory = builder.build(is);
//1.4 关流
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//2. 对外暴露一个方法,共他人获取连接
public static SqlSession getSession(){
return factory.openSession();
}
//3. 关闭session
public static void closeSession(SqlSession session){
session.close();
}
//4. 关闭session,关闭之前先提交
public static void closeSessionAndCommit(SqlSession session){
session.commit();
session.close();
}
}
新增用户
接口中添加方法
public interface UserDao { /** * 新增用户 * @param user * @return 影响的行数 */ int add(User user); }
在 UserDao.xml 文件中加入新增配置
<!--2. 添加用户 id要写方法名字 parameterType :参数的类型 增删改不需要写返回类型,默认mybatis会自己把影响的行数给返回。 #{} 就是取对象中的属性值,当然也可以取参数的值。 --> <insert id="add" parameterType="com.zml.pojo.User"> insert into t_user values(null , #{username} , #{sex} , #{birthday} , #{address}) </insert>
添加测试类中的测试方法
@Test public void testAdd() throws IOException { //1. 读取核心配置文件 InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml"); //2. 创建构建器 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //3. 创建sqlsessionfactory SqlSessionFactory sessionFactory = builder.build(is); //4. 创建sqlsession SqlSession session = sessionFactory.openSession(); //5. 创建代理 //Type interface com.zml.dao.UserDao is not known to the MapperRegistry. UserDao userDao = session.getMapper(UserDao.class); //6. 调用方法 User user = new User(); user.setUsername("admin"); user.setSex("男"); user.setBirthday(new Date()); user.setAddress("深圳"); int row = userDao.add(user); System.out.println("row=" + row); // 新增操作执行完毕之后,即可获取到user对象的id值, //但是目前获取不了,因为这个方法add里面的U映射文件没有配置 //System.out.println("id=" + user.getUid()); //7. 默认情况下mybatis把连接的自动提交的事务给关闭了,所以只要是增删改,都需要手动提交事务 session.commit(); //8. 关闭session session.close(); }
新增用户 id 的返回值
有时候我们存在一些场景,把一条数据存储到数据库了之后,需要马上得到这条记录的id值。因为这条数据是我们自己创建赶出来的,它的id是多少,我们是不知道的,只有这条记录已经存在于数据库里面了,才知道它的id值。
什么情况下|场景下,我们需要在插入完这条记录之后,立即就需要得到这条记录的id呢?
当我们往A表里面添加一条记录之后,需要继续往B表里面添加一条记录,但是A表和B表形成了一种主外键关系。B表里面的外键就是A表里面的主键,所以往B表里面添加记录,必须要先知道A表的这条记录的主键
新增用户后, 同时还要返回当前新增用户的 id 值,因为 id 是由数据库的自动增长来实现的,所以就相当于我们要在新增后将自动增长 auto_increment 的值返回。
方式一 标签SelectKey获取主键
属性 描述 keyProperty selectKey 语句结果应该被设置的目标属性。 resultType 结果的类型。MyBatis 通常可以算出来,但是写上也没有问题。MyBatis 允许任何简单类型用作主键的类型,包括字符串。 order 这可以被设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它会首先选择主键,设置 keyProperty 然后执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 元素-这和如 Oracle 数据库相似,可以在插入语句中嵌入序列调用。
<!--新增用户之后,获取id值--> <insert id="add" parameterType="com.zml.pojo.User"> <!-- 设置添加完毕之后,返回id值 selectKey : 用于返回id主键 keyProperty :使用User里面的什么属性来装id的值。 resultType :主键的类型,java类型 order : 这个查询主键的动作,是发生在添加完记录之后再执行还是添加记录之前就执行? 这里面能写的只有两个: BEFORE | AFTER 一般写AFTER --> <selectKey keyProperty="uid" resultType="int" order="AFTER"> <!--这里面还要写一串固定的语法 : 表示查询上一次插入完毕之后的id值是多少。--> select LAST_INSERT_ID() </selectKey> insert into t_user values(null , #{username} , #{sex} , #{birthday} , #{address}) </insert>
方式二属性配置
在isnert标签里面直接使用属性keyProperty 和 useGeneratedKeys来设置获取主键id值。
<!-- 新增用户之后,获取id值 : 方式二 keyProperty : 使用user里面的什么属性来装主键 useGeneratedKeys : 使用数据库的自增的这一套方法,使用数据库的主键id. 一般写就是true , 对应于mysql的数据库,这里写的就是true 如果写成false,就表明不使用数据库自增的那一套方法得到的id , 使用mybatis自己维护的id值。 也就是说,id值,由mybatis提供。 oracle 不支持自增。 --> <insert id="add" parameterType="com.zml.pojo.User" keyProperty="uid" useGeneratedKeys="true"> insert into t_user values(null , #{username} , #{sex} , #{birthday} , #{address}) </insert>
**新增用户 id 的返回值(字符串类型)** - 主键不一定是int类型... 主键也可以是字符串类型。只有到以后做项目,有数据库合并,数据库集群的时候。 字符串类型的主键通常就是UUID生成的一串32个字符的字符串。 数据库合并! <!--新增用户之后,获取id值 字符串--> <insert id="add" parameterType="com.zml.pojo.User"> <selectKey keyProperty="uid" resultType="String" order="AFTER"> <!--由mybatis自己维护主键,返回uuid生成的主键值。--> select uuid() </selectKey> insert into t_user values(null , #{username} , #{sex} , #{birthday} , #{address}) </insert>
修改用户
接口中添加修改方法
User findByUid(int id); /** 要想更新用户,必须先查询用户。 * 更新用户 * @param user * @return */ int update(User user);
在 UserDao.xml 文件中加入新增配置
<!--更新用户--> <!--1. 先根据UID来查询用户--> <select id="findByUid" parameterType="int" resultType="com.zml.pojo.User"> select * from t_user where uid = #{id} </select> <!--2. 更新用户--> <update id="update" parameterType="com.zml.pojo.User"> update t_user set username = #{username} , sex = #{sex} , birthday = #{birthday} , address = #{address} where uid = #{uid} </update>
添加测试类中的测试方法
//更新的测试 @Test public void testFindById(){ //找人 User user = userDao.findByUid(3); System.out.println("user=" + user); //修改地址 user.setAddress("深圳"); //更新。 int row = userDao.update(user); System.out.println("row=" + row); session.commit(); }
删除用户
UserDao中添加新增方法
public interface UserDao { /** * 删除用户 * @param id * @return 影响的行数 */ int delete(int id); }
在 UserDao.xml 文件中加入新增配置
<!--删除用户 根据id来删除用户。 --> <delete id="delete" parameterType="int"> delete from t_user where uid = #{id} </delete>
添加测试类中的测试方法
//删除的测试 @Test public void testDelete(){ int row = userDao.delete(5); System.out.println("row=" + row); //要记得提交事务 session.commit(); }
模糊查询
方式一
UserDao 中添加根据姓氏来查询用户的方法
//模糊查询: 根据用户的姓氏|首字母来查询用户 List<User> findUserByFirstName(String name);
在 UserDao.xml 文件中加入新增配置
<!--模糊查询: 根据姓氏|首字母查询用户--> <select id="findUserByFirstName" resultType="com.zml.pojo.User" parameterType="java.lang.String"> select * from t_user where username like #{name} </select>
添加测试类中的测试方法
//模糊查询: 根据姓氏|首字母查询用户 @Test public void testFindUerByFirstName(){ List<User> list = userDao.findUserByFirstName("a%"); System.out.println("list=" + list); }
方式二
UserDao 中添加新增方法
public interface UserDao { //模糊查询: 根据用户的姓氏|首字母来查询用户 List<User> findUserByFirstName02(String name); }
在 UserDao.xml 文件中加入新增配置
<select id="findUserByFirstName02" resultType="com.zml.pojo.User" parameterType="java.lang.String"> select * from t_user where username like '${value}' </select>
添加测试类中的测试方法
//模糊查询: 根据姓氏|首字母查询用户 @Test public void testFindUerByFirstName02(){ List<User> list = userDao02.findUserByFirstName02("a%"); System.out.println("list02222=" + list); }
#{}与${}的区别
#{}
可以防止sql注入
会对sql语句进行预编译|解析,传递什么参数进来,仅仅是顶替占位
#{}
而已。一般使用的都是这个#{}
#{} 背后会自动的拼接上 ''
${}
不能防止sql注入
需要手动添加单引号
如果只有一个属性的话必须使用value '${value}'
不会对sql语句进行预先编译,传递什么参数进来,不会仅仅认为这数据,会和sql语句做拼接之后再解析SQL语句 ' or '1=1'
一般比较少用这个,能使用#{}就先使用这个#{}
${} 不会拼接上 '' 所以有时候,我们需要做一些order by 这样的列名指定。
一般来说,只要 #{} 能用的,基本都用它,只要它不能用的时候,再想想这个 ${}
Mybatis的参数深入
parameterType
传递简单类型
基本的类型,字符串
直接写
#{任意字段}
或者${value}
接口
//传递参数: 传递简单的参数 User findByUserName(String username);
映射文件
<!-- 1. 传递简单的参数 parameterType :表示参数的类型。 写全路径 要想获取单一的参数,直接使用#{} 或者 ${} 也行,然后括号里面就写参数的名字 当然里面也可以写别的,但是一般都写参数的名字,方便阅读。 User findByUserName(String username); --> <select id="findByUserName" parameterType="java.lang.String" resultType="com.zml.pojo.User"> select * from t_user where username = #{username} </select>
传递 pojo 对象
Mybatis 使用 ognl 表达式解析对象字段的值, #{}或者${}括号中的值为 pojo 属性名称。
接口
//传递参数: 传递对象类型的参数 User findByUserName(User user );
映射文件
<!-- 2. 传递对象类型的参数 2.1 parameterType :表示参数的类型,这里传递过来的是一个对象 2.2 要想从对象里面取出它的某一个属性,直接写属性名即可,无需使用对象.属性这种写法。 User findByUserName(User user ); 2.3 属性的名字必须要写对,也不能乱写。 --> <select id="findByUserName" parameterType="com.zml.pojo.User" resultType="com.zml.pojo.User"> select * from t_user where username = #{username} </select>
传递 pojo 包装对象类型
开发中通过 pojo 传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。Pojo 类中包含 pojo。
pojo
@Data @NoArgsConstructor @AllArgsConstructor public class QueryVo { private User user ; }
接口
//传递参数: 传递包装对象类型 User findByUserName(QueryVo queryVo);
映射文件
<!-- 3. 传递包装对象类型的参数 3.1 parameterType : 传递的参数类型 3.2 传递过来的是一个QueryVo的对象,它的里面有一个属性叫做User 3.3 User这个对象里面,有一个属性username 3.4 现在想使用username来查询用户数据。 写属性的时候,直接从参数类型里面的属性开始找起就行了。 --> <select id="findByUserName" parameterType="com.zml.pojo.QueryVo" resultType="com.zml.pojo.User"> select * from t_user where username = #{user.username} </select>
测试类
//传递包装对象的参数 @Test public void testFindByUserName03(){ //1. 获取sqlsession SqlSession session = SqlSessionFactoryUtil.getSession(); //2. 获取代理 UserDao userDao = session.getMapper(UserDao.class); //3. 调用方法 QueryVo qv = new QueryVo(); User user = new User(); user.setUsername("ls"); qv.setUser(user); //传递一个queryvo对象进去,根据用户名来查询用户 User u = userDao.findByUserName03(qv); System.out.println("u=" + u); //4. 关闭session SqlSessionFactoryUtil.closeSession(session); }
传递多个参数
在方法的参数上打上注解@param
如果方法有传递多个参数的必要,那么通常有四种写法来实现:
使用#{0} , #{1} , #{2} ... 来获取指定位置的参数。 0 表示第一个参数,1表示第二个参数。阅读性差。
使用注解@param来给参数起名字。然后在xml里面使用#{参数的别名}取值
可以使用一个Map集合来封装这些数据,然后传递map集合进去。
可以使用一个javaBean来包装这些数据,然后传递javabean的对象进去。
接口
public interface UserDao { //根据多个参数来找人。 //传递参数: 传递多个参数 使用@Param来给参数起别名 User findUser(@Param("username") String username , @Param("uid") int uid); }
映射文件
<!-- ========================传递多个参数============ 1. 采用下标计数法, 0 就表示第一个参数, 1,就表示第二个参数,以此类推... 一般也不建议使用这种,因为这种方式可读性比较差。数字的可读性比较差 2. 采用注解的方式。 在方法的参数上,使用注解@param标记这个参数的名字是什么,然后再xml里面使用#{}来取值 3. 后面还有很多种法子,但是都是一样的套路,就是把多个参数包装成一个整体 a. 包装成一个对象, b. 包装成一个map c. 包装到一个list --> <!--4. 传递多个参数--> <select id="findUser" resultType="com.zml.pojo.User"> select * from t_user where username = #{username} and uid = #{uid} </select>
resultType
输出简单类型
直接写对应的Java类型. eg: 返回int
<!-- 1. 返回数据: 返回简单的数据类型 restultType :表示返回的结果类型,如果返回的是一个简单的数字或者字符串 那么直接写类型的名字即可 int , string --> <select id="findCount" resultType="int"> select count(*) from t_user </select>
输出pojo对象
直接写当前pojo类的全限定名 eg: 返回User
<!--2. 返回数据: 返回对象类型的数据 restultType :表示返回的结果类型 如果返回的是一个对象类型,那些的resultType 就是这种类型的全路径 --> <select id="findByUid" parameterType="int" resultType="com.zml.pojo.User"> select * from t_user where uid = #{uid} </select>
输出pojo列表
直接写当前pojo类的全限定名 eg: 返回 List<User> list;
<!-- 3. 返回集合类型 resultType :返回的数据类型 如果返回的是一个集合,那么不要写集合的类型,只需要写集合里面的元素类型即可。 --> <select id="findAll" resultType="com.zml.pojo.User"> select * from t_user </select>
resultMap结果类型
esultType可以指定pojo将查询结果映射为pojo,但需要pojo的属性名和sql查询的列名一致方可映射成功。
如果sql查询字段名和pojo的属性名不一致,可以通过resultMap将字段名和属性名作一个对应关系 ,resultMap实质上还需要将查询结果映射到pojo对象中。
resultMap可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和list实现一对一查询和一对多查询 (多表联合查询)。(下次课讲)
实体:数据库字段是sex 实体对应的是sex66的情况
@Data @NoArgsConstructor @AllArgsConstructor public class User { private int uid; private String name; private String sex66; private Date birthday; private String address; }
接口
public interface UserDao { //返回对象类型: 返回对象 User findByUid(int uid); }
映射文件
<!-- 解法二: 使用resultMap 来进行数据的映射(属性和列进行映射) 1. 如果返回的属性名和列名不一样,那么不能使用resultType,必须要使用resultMap 2. resultMap其实就是一种结果映射。 3. resultMap标签主要是用来做映射的, id : 表示唯一的标识,给这种映射起一个标识。 type : 这种映射最终封装出来的数据是什么类型。 4. id这个标签比较特殊一些,官方建议使用这个标签来映射主键 5. result标签主要是用来映射普通的列 column : 表示列的名字 property : 表示类里面属性的名字 如何列名和属性名称相同的话在resultMap中不写对应的属性名 --> <resultMap id="userMap" type="com.zml.pojo.User"> <id column="uid" property="uid" /> <result column="username" property="username"/> <result column="sex" property="sex66"/> <!-- 如何列名和属性名称相同的话在resultMap中不写对应的属性名 --> <result column="birthday" property="birthday"/> <result column="address" property="address"/> </resultMap> <!--resultMap 就是告诉mybatis,使用这种映射规则来封装数据--> <select id="findByUid" parameterType="int" resultMap="userMap"> select * from t_user where uid = #{uid} </select>
Mybatis 的动态SQL
动态 SQL 之if标签
QueryVo.java
它的作用专门是用来包装查询对象的。
@Data @NoArgsConstructor @AllArgsConstructor public class QueryVo { private User user ; }
UserDao.java接口
public interface UserDao { //根据组合条件(id , username)来查询用户 User findUser(QueryVo qv); }
UserDao.xml接口映射文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zml.dao.UserDao"> <!--根据组合条件来查询用户: id 和 username--> <select id="findUser" parameterType="queryvo" resultType="user"> <!-- 这里的1=1只是一个永远为真的条件,它的作用仅仅是为了保证这个where关键字存在一次而已。--> select * from t_user where 1=1 <!-- 判定id这个值是否不为空,如果不为空,则需要把id添加到查询条件里面去 如果queryVo里面的user属性不为空,并且user里面的id属性也有值,那么 即表示想要使用uid来进行条件查询。 --> <if test="user != null and user.uid != 0"> and uid = #{user.uid} </if> <!--判定username 是否有值,有值就给它加入到查询条件中--> <if test="user != null and user.username != null"> and username = #{user.username} </if> </select> <!--select * from t_user where uid = ? and username = ?--> </mapper>
测试类
@Test public void testFindUser(){ SqlSession session = SqlSessionFactoryUtil.getSession(); UserDao userDao = session.getMapper(UserDao.class); //设置查询条件 QueryVo qv = new QueryVo(); User user = new User(); // user.setUid(2); user.setUsername("ls"); qv.setUser(user); User newUser = userDao.findUser(qv); System.out.println("newUser=" + newUser); session.close(); }
动态 SQL 之where标签
为了简化上面 where 1=1 的条件拼装,我们可以采用<where>标签来简化开发。
<?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.zml.dao.UserDao02"> <!--需求: 根据性别和用户名的组合条件来查询用户--> <select id="findUser" parameterType="queryVo" resultType="user"> select * from t_user <where> <!--判断用户名是否为空,如果不为空,则添加到查询条件中--> <if test="user !=null and user.username !=null "> and username = #{user.username} </if> <!-- 判断性别是否为空,如果不为空,则添加到查询条件中--> <if test="user != null and user.sex != null"> and sex = #{user.sex} </if> </where> </select> <!--select * from t_user--> <!--select * from t_user where username = ? and sex = ? --> </mapper>
SQL 片段
Sql 中可将重复的 sql 提取出来,使用时用 include 引入进来,最终达到 sql 重用的目的。我们先到 UserDao.xml 文件中使用<sql>标签,定义出公共部分.
<!--提取共性的sql语句,把这些共性的sql语句称之为 sql片段--> <sql id="selectAllSql"> select * from t_user </sql> <select id="findUserByVo04" parameterType="queryVo" resultType="user"> <!-- select * from t_user --> <include refid="selectAllSql"></include> <!-- select * from t_user where username like '张%' and uid in(7,8,9); --> <where> <if test="user != null and user.username != null"> and username like '${user.username}%' </if> <!-- and uid in(7,8,9) --> <if test="ids != null"> <!--collection: 遍历的集合 list: 7,8,9 item: 遍历出来的每一个元素 ,7 |8 |9 open : 在开始拼接的语句 separator : 每一次拼接id值之后的间隔字符 close: 最终拼接的语句 --> <foreach collection="ids" item="id" open="and uid in (" separator="," close=")"> #{id} </foreach> </if> </where> </select>
动态标签之foreach标签
传入多个 id 查询用户信息,用下边sql 实现:
select uid ,username ,birthday ,sex, address from t_user WHERE uid =1 OR uid =2 OR uid=6
这样我们在进行范围查询时,就要将一个集合中的值,作为参数动态添加进来。这样我们将如何进行参数的传递?
dao接口
public interface UserDao03 { //查询多个id的用户 List<User> findUser(@Param("ids") List<Integer> ids); }
映射文件
<?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.zml.dao.UserDao03"> <!-- 需求: 传递进来一个集合,集合里面装很多的id,也有可能不装id,根据这个条件来查询用户--> <!-- parameterType: 参数是什么类型就写什么类型。 resultType: 返回的结果类型,如果返回的是一个集合,那么写它里面的元素类型 如果传递进来的集合里面装 1, 2, 3 ,那么即表示把id为 1 ,id为2,id为3的用户查询出来 select uid ,username ,birthday ,sex, address from t_user WHERE uid =1 OR uid =2 OR uid=6 select * from t_user where uid = 1 or uid = 2 or uid = 3 or uid =4 --> <select id="findUser" parameterType="list" resultType="user"> select * from t_user <where> <!--判断真的有传集合进来 --> <if test="ids != null"> <!-- 遍历集合,取出来每一个id值,然后拼接到上面的sql语句去 collection :遍历的集合是什么 item :遍历出来的每一个元素,用什么名字的变量来接受 open: 遍历开始之前先补什么字符串到上面的sql语句去 close: 遍历结束之后,在上面的sql语句拼接什么内容 separator : 遍历每一次之后,间隔什么内容,拼接到sql语句去。 --> <foreach collection="ids" item="id" open="uid =" separator=" or uid =" close=""> #{id} </foreach> </if> </where> </select> </mapper>
测试类
@Test public void testFindUser(){ SqlSession session = SqlSessionFactoryUtil.getSession(); UserDao03 userDao = session.getMapper(UserDao03.class); List<Integer> list = new ArrayList<Integer>(); //list.add(1); //list.add(2); //list.add(3); List<User> userList = userDao.findUser(list); System.out.println("userList=" + userList); session.close(); }
select uid ,username ,birthday ,sex, address from t_user WHERE uid IN (1,2,3)
<?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.zml.dao.UserDao04"> <!-- 需求: 传递进来一个集合,集合里面装很多的id,也有可能不装id,根据这个条件来查询用户 如果传递进来的集合里面装1,2,3 sql : select * from t_user where uid in (1,2,3) --> <select id="findUser" parameterType="list" resultType="user"> select * from t_user <where> <!--判定是否真的有集合 --> <if test="ids != null"> <!-- select * from t_user where uid in ( 1,2,3) 遍历集合 collection :遍历的集合 参数ids item : 遍历出来的每一个元素用什么来接 open: 拼接这些遍历出来的数据之前,先拼接什么到sql语句去。 separator : 数据的间隔是什么。 close : 最终这个语句的末尾是什么。 --> <foreach collection="ids" item="id" open="uid in (" separator="," close=")"> #{id} </foreach> </if> </where> </select> </mapper>
Mybatis 的多表关联查询
一(多)对一
本次案例以简单的用户和账户的模型来分析 Mybatis 多表关系。用户为 User 表,账户为Account 表。一个用户(User)可以有多个账户(Account),但是一个账户(Account)只能属于一个用户(User)。 类比到生活中的: 一个人可以有多张银行卡账户,但是一个银行卡账户只能属于一个人。具体关系如下:
查询所有账户信息, 关联查询账户的用户名和地址
因为一个账户信息只能供一个用户使用,所以从查询账户信息出发关联查询用户信息为一对一查询。
SELECT a.*,u.username,u.address FROM t_account a,t_user u WHERE a.uid = u.uid
内连接Account.java
@Data @AllArgsConstructor @NoArgsConstructor public class Account { private int aid; private double money; private int uid; //在账户这边,体现这个账户属于哪一个用户。 //一个账户只能属于一个用户,一般这种情况采用对象来表示。 private User user01; }
User.java
@Data @NoArgsConstructor @AllArgsConstructor public class User02 { private int uid; private String username; private String sex; private Date birthday; private String address; }
AccountDao.java
public interface AccountDao02 { //查询所有的账户 , 返回值是一个List集合,里面装Account //因为Account里面有一个属性叫做user01,所以这个Account 也足以表示完所有的数据了。 List<Account> findAll(); }
AccountDao.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zml.dao.AccountDao"> <!-- resultMap : 表的列和bean里面的属性映射 id: 这种映射的名字, 唯一标识符 type: mybatis封装好数据之后,最终产出的是一个什么类型的对象呀--> <resultMap id="accountMap" type="account"> <id column="aid" property="aid"/> <result column="money" property="money"/> <result column="uid" property="uid"/> <!-- 一个账户只能属于一个用户,那么账户和用户的关系其实就是一(多)对一 association : 主要是用来表示一对一的关系,即表示这个账户属于哪一个用户 property: user01 表示Account这个类里面的属性 javaType : Account这个类里面的属性user01是什么类型。 result 标签, 其实就是表示把剩下的address和 username属性封装到 user02这个类里面的username和address属性上 --> <association property="user01" javaType="User"> <result column="username" property="username"/> <result column="address" property="address"/> </association> </resultMap> <!--由于下面执行的这条语句是查询两张表的数据,返回的结果 mybatis并不能直接使用Account来装,因为有些列的数据(username, address) mybatis不知道怎么装,所以我们需要建立映射规则 , 这里就必须使用resultMap属性 --> <select id="findAll" resultMap="accountMap"> <!-- 查询所有账户信息, 关联查询账户的用户名和地址 --> select a.* , u.username , u.address from t_account a , t_user u where a.uid = u.uid; </select> </mapper>
一对多
需求: 查询所有的用户,以及这个用户有哪些账户,都查询出来。
sql : 不能使用内连接了,内连接的核心就是查询出来两张表都有对等关系的记录信息。但是有一种极端的情况: 有的用户可能没有账户。此时就必须使用外连接。其实左外和右外都可以,只是他们的区别就是谁在左边谁在右边而已。
SELECT u.*, a.aid, a.money FROM t_user u LEFT OUTER JOIN t_account a ON u.uid = a.uid
Account.java
@Data @AllArgsConstructor @NoArgsConstructor public class Account { private int aid; private double money; private int uid; }
User.java
为了能够让查询的 User 信息中,带有他的个人多个账户信息,我们就需要在 User 类中添加一个集合, 用于存放他的多个账户信息,这样他们之间的关联关系就保存了。
@Data @NoArgsConstructor @AllArgsConstructor public class User { private int uid; private String username; private String sex; private Date birthday; private String address; //一个用户是可以有多个账户的。 //如何表示一个用户有多个账户呢? //在一的一边,表示多的那一边的关系,通常采用List集合来表示 private List<Account> accountList; }
UserDao.java
public interface UserDao { //查询所有的用户 List<User> findAll(); }
UserDao.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zml.dao.UserDao"> <!-- resultMap: 建立映射关系 id: 这种关系的名字,唯一的标识符 type : mybatis根据这种关系封装出来的数据对象是什么类型 --> <resultMap id="userMap" type="user"> <id column="uid" property="uid"/> <result column="username" property="username"/> <result column="sex" property="sex"/> <result column="birthday" property="birthday"/> <result column="address" property="address"/> <!-- 一个用户可以拥有多个账户,下面要表示这个用户的账户信息都如何映射 即表示多的那一边的属性如何映射 collection标签: 用来表示多的那边数据怎么映射 property: User这个类里面的属性accountList ofType : 表示accountList这个集合里面装的元素类型 --> <collection property="accountList" ofType="account"> <id column="aid" property="aid"/> <result column="money" property="money"/> <result column="auid" property="uid"/> </collection> </resultMap> <!--查询两张表,得到的数据,不可能直接使用resultType来封装--> <select id="findAll" resultMap="userMap"> <!-- 使用左外连接来实现 优先考虑左边的表,左边的表一定全部查询出来,不管有没有账户 由于mybatis查询两张表的时候,如果存在同名的列,有可能会发生数据错乱的情况,所以需要给其中一个同名的列起别名 比如现在账户表里面的uid 起别名叫做 auid --> select u.*,a.aid , a.money , a.uid auid from t_user u left join t_account a on u.uid = a.uid; </select> </mapper>
多对多
需求:实现查询所有角色对象并且加载它所分配的用户信息。
CREATE DATABASE mybatis_day01; USE mybatis_day01; CREATE TABLE t_user( uid int PRIMARY KEY auto_increment, username varchar(40), sex varchar(10), birthday date, address varchar(40) ); INSERT INTO `t_user` VALUES (null, 'zs', '男', '2018-08-08', '北京'); INSERT INTO `t_user` VALUES (null, 'ls', '女', '2018-08-30', '武汉'); INSERT INTO `t_user` VALUES (null, 'ww', '男', '2018-08-08', '北京'); CREATE TABLE t_role( rid INT PRIMARY KEY AUTO_INCREMENT, rName varchar(40), rDesc varchar(40) ); INSERT INTO `t_role` VALUES (null, '校长', '负责学校管理工作'); INSERT INTO `t_role` VALUES (null, '副校长', '协助校长负责学校管理'); INSERT INTO `t_role` VALUES (null, '班主任', '负责班级管理工作'); INSERT INTO `t_role` VALUES (null, '教务处主任', '负责教学管理'); INSERT INTO `t_role` VALUES (null, '班主任组长', '负责班主任小组管理'); -- 中间表(关联表) CREATE TABLE user_role( uid INT, rid INT ); ALTER TABLE user_role ADD FOREIGN KEY(uid) REFERENCES t_user(uid); ALTER TABLE user_role ADD FOREIGN KEY(rid) REFERENCES t_role(rid); INSERT INTO `user_role` VALUES ('1', '1'); INSERT INTO `user_role` VALUES ('3', '3'); INSERT INTO `user_role` VALUES ('2', '3'); INSERT INTO `user_role` VALUES ('2', '5'); INSERT INTO `user_role` VALUES ('3', '4');
查询角色我们需要用到 Role 表,但角色分配的用户的信息我们并不能直接找到用户信息,而是要通过中间表(USER_ROLE 表)才能关联到用户信息。 下面是实现的 SQL 语句:
左外连接: -- 只要用户的数据和角色表的数据 select r.* , u.* from t_role r left join user_role ur on r.rid = ur.rid left join t_user u on ur.uid = u.uid
User.java
@Data @NoArgsConstructor @AllArgsConstructor public class User { private int uid; private String username; private String sex; private Date birthday; private String address; }
Role.java
@Data @NoArgsConstructor @AllArgsConstructor public class Role { private int rid; private String rName; private String rDesc; //一个角色可以有很多的用户担任,那么体现的就是一对多的关系 private List<User> userList; }
RoleDao.java
public interface RoleDao { List<Role> findAll(); }
RoleDao.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zml.dao.RoleDao"> <!-- resultMap: 做映射出来 id :唯一标识符 type : 封装好了这些数据最终是什么类型的数据 --> <resultMap id="roleMap" type="Role"> <id column="rid" property="rid"/> <result column="rName" property="rName"/> <result column="rDesc" property="rDesc"/> <!-- 一个角色是可有多个用户担任的!--> <collection property="userList" ofType="user"> <id column="uid" property="uid"/> <result column="username" property="username"/> <result column="sex" property="sex"/> <result column="birthday" property="birthday"/> <result column="address" property="address"/> </collection> </resultMap> <!--查询所有的角色信息,并且把这个角色有谁担任的用户信息给查询出来--> <select id="findAll" resultMap="roleMap"> select r.* , u.* from t_role r left join user_role ur on r.rid = ur.rid left join t_user u on ur.uid = u.uid </select> </mapper>
MyBatis缓存
作用:
将数据源(数据库或者文件)中的数据读取出来存放到缓存中,再次获取的时候 ,直接从缓存中获取,可以减少和数据库交互的次数,这样可以提升程序的性能!
MyBatis缓存类别
一级缓存:它是sqlSession对象的缓存,自带的(不需要配置)不可卸载的(不想使用还不行). 一级缓存的生命周期与sqlSession一致。
二级缓存:它是SqlSessionFactory的缓存。只要是同一个SqlSessionFactory创建的SqlSession就共享二级缓存的内容,并且可以操作二级缓存。二级缓存如果要使用的话,需要我们自己手动开启(需要配置的)。
一级缓存
一级缓存: 依赖sqlSession对象的, 自带的不可卸载的. 一级缓存的生命周期和sqlSession一致
一级缓存清空
sqlSession销毁 , 调用close()
增删改 提交之后 , 调用了commit
//证明一级缓存的存在 // 前提是开启了log4j日志打印功能,显示出来查询数据 @Test public void testFindByUid(){ SqlSession session = SqlSessionFactoryUtil.getSession(); UserDao userDao = session.getMapper(UserDao.class); //查询uid为1的用户数据 User user = userDao.findByUid(1); System.out.println("user=" + user); System.out.println("-------------------------------------------"); //如果这里查询的数据跟前面查询的数据是一样的,都是查询id为1的数据,那么此次查询就不用去查询数据库了 //而是直接从一级缓存里面拿出来 User user2 = userDao.findByUid(1); System.out.println("user2=" + user2); //关闭session session.close(); } //第一次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息。 如果 sqlSession 去执行 commit操作(执行插入、更新、删除),清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。 commit动作一定会清空缓存,因为有可能存在一种假设: 原来查询的是id为1的用户信息,接着做了更新操作,把id为1的用户信息给修改了,那么此时缓存里面的数据就是过期数据。
二级缓存
二级缓存是SqlSessionFactory的缓存。只要是同一个SqlSessionFactory创建的SqlSession就共享二级缓存的内容,并且可以操作二级缓存. 默认mybatis不会开启二级缓存,需要手动配置
在 SqlMapConfig.xml 文件开启二级缓存
<!--配置--> <configuration> <!-- 引入properties文件--> <properties resource="db.properties"/> <!-- 开启redis二级缓存 默认就是开启的可以不进行设置--> <settings> <setting name="cacheEnabled" value = "true"/> </settings> </configuration>
二级缓存默认是开启的,当然我们可以手动关闭
==因为 cacheEnabled 的取值默认就为 true==,所以这一步可以省略不配置。为 true 代表开启二级缓存;为 false 代表不开启二级缓存。
配置相关的 Mapper 映射文件
默认情况下mybatis已经开启了二级缓存,但是默认情况下所有的查询动作都不会把数据放在二级缓存里面。
如果希望某一个查询的动作,把数据存放在二级缓存里里面,那么需要在映射文件中配置
<cache>
标签表示当前这个 mapper 映射将使用二级缓存,即:该mapper文件中的所有查询操作都将使用二级缓存, 无需单独为每一个<select>标签开启。<mapper namespace="com.zml.dao.UserDao"> <!--cache 标签会开启该映射文件下的所有查询语句的二级缓存,如果有查询语句不想使用二级缓存的话可以在select标签中天健cache属性进行设置--> <cache/> <!-- 若不想让某个`<select>`标签使用二级缓存,则可以把useCache设置为false 。 注意: 针对每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存。 --> <select id="findByUid" parameterType="int" resultType="user" useCache="false"> select * from t_user where uid = #{uid} </select> </mapper>
测试类
public class TestUserDao { //证明二级缓存的存在 @Test public void testFindByUid(){ SqlSession session1 = SqlSessionFactoryUtil.getSession(); UserDao userDao = session1.getMapper(UserDao.class); User user = userDao.findByUid(13); System.out.println("user=" + user); session1.close(); System.out.println("---------------------------"); SqlSession session2 = SqlSessionFactoryUtil.getSession(); UserDao userDao2 = session2.getMapper(UserDao.class); User user2 = userDao2.findByUid(13); System.out.println("user2=" + user2); session2.close(); //删除一个 /* SqlSession session6666 = SqlSessionFactoryUtil.getSession(); UserDao dao6666 = session6666.getMapper(UserDao.class); int row = dao6666.deleteByUid(14); System.out.println("row=" + row); session6666.commit(); session6666.close();*/ System.out.println("---------------------------"); SqlSession session3 = SqlSessionFactoryUtil.getSession(); UserDao userDao3 = session3.getMapper(UserDao.class); User user3 = userDao3.findByUid(13); System.out.println("user3=" + user3); session3.close(); } }
注意事项
只有出现了增删改的动作,才会清空二级缓存。而不能简单的以commit这个方法的调用来下结论。
当我们在使用二级缓存时,缓存的类一定要实现 java.io.Serializable 接口,这种就可以使用序列化方式来保存对象。
Mybatis延迟加载策略
延迟加载:就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.
坏处: 因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。
好处: 先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快.
懒加载只有在多表联合查询的时候才会出现,只查单张表的时候,不存在什么懒加载
使用 Assocation 实现延迟加载 (多(一)对一)
查询账户(Account)信息并且关联查询用户(User)信息。
所有的懒加载查询工作,都不能使用多张表的联合查询了(内连接|外连接)
必须把这些工作拆分成查单张表的工作
先查询所有的账户信息 : select * from t_account
得到所有的账户了之后, select * from t_user where uid = ?
User.java
@Data @NoArgsConstructor @AllArgsConstructor public class User { private int uid; private String username; private String sex; private Date birthday; private String address; }
Account.java
@Data @NoArgsConstructor @AllArgsConstructor public class Account { private Integer aid; private Integer uid; private Double money; //表示账户属于哪一个人。 //一个账户只能属于一个人。 private User user ; }
AccountDao.java
public interface AccountDao { //找所有的账户 List<Account> findAll(); }
AccountDao.xml
<mapper namespace="com.zml.dao.AccountDao"> <!--resultMap: 作用就是建立映射关系。--> <resultMap id="accountMap" type="account"> <id column="aid" property="aid"/> <result column="money" property="money"/> <result column="uid" property="uid"/> <!-- 账户类里面有一个属性叫做User 这个user的数据必须要去查询用户表才能得到 association: 一(多)对一的关系体现 property: Account里面的属性user fetchType: lazy :表示懒加载,这个user的数据线别拿,如果真的要拿,配合下面的 select属性来说 select : 指定UserDao的findByUid方法,意思就是上面如果要查询user的数据就来调用这个方法 写法: 包名.类名.方法名 column: uid , 调用findByUid方法的时候,把uid这一个列的数据传过去。 --> <association property="user" fetchType="eager" column="uid" select="com.zml.dao.UserDao.findByUid"/> </resultMap> <!--1. 虽然懒加载的第一步是查询第一张表的所有数据回来,即使他们的列名和属性名都一样, 但是这里也不能使用resultType来封装数据 2. 需要用resultMap来封装数据,只有使用了resultMap来封装数据,才有可能去牵扯出来 用户的信息。否则现在不知道用户的信息从哪里来了 3. 也就是第二步的查询不知道怎么走了!--> <select id="findAll" resultMap="accountMap"> select * from t_account </select> </mapper>
UserDao.java
public interface UserDao { //懒加载:一对一的第二步,根据用户的id来查询用户 User findByUid(int uid); }
UserDao.xml
<mapper namespace="com.zml.dao.UserDao"> <select id="findByUid" parameterType="int" resultType="user"> select * from t_user where uid = #{uid} </select> </mapper>
测试
public class TestAccoutDao { //查询所有的账户,并且把与之关联的用户数据给查询出来(懒记载) @Test public void testFindAll(){ SqlSession session = SqlSessionFactoryUtil.getSession(); AccountDao accountDao = session.getMapper(AccountDao.class); List<Account> list = accountDao.findAll(); //System.out.println("list=" + list); session.close(); /* System.out.println("-------------------------------------------"); //关闭连接之后再看数据(查询) Account account = list.get(0); User user = account.getUser(); System.out.println("name="+user.getUsername());*/ } }
Collection 实现延迟加载 (一对多,多对多)
查询所有的用户信息,并且把这个用户拥有的账户信息也查询出来
Account.java
@Data @NoArgsConstructor @AllArgsConstructor public class Account03 { private Integer aid; private Integer uid; private Double money; }
User.java
@Data @NoArgsConstructor @AllArgsConstructor public class User implements Serializable { private int uid; private String username; private String sex; private Date birthday; private String address; //表示一个用户有很多的账户。 private List<Account> accountList; }
UserDao.java
public interface UserDao { //懒加载的第一步,查询用户表的所有数据 List<User> findAll(); }
UserDao.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zml.dao.UserDao"> <resultMap id="userMap" type="user"> <id column="uid" property="uid"/> <result column="username" property="username"/> <result column="sex" property="sex"/> <result column="address" property="address"/> <result column="birthday" property="birthday"/> <!--查询用户表得到的结果,只能封装成User的对象数据,没有办法封装成账户数据 如果想要封装这个用户都有哪些账户,那么需要去查询另一张表 其实就是指:要去调用AccountDao 告诉mybatis,当遇到属性accountList的时候,稍微偷懒一会。 等真的要查看它的数据的时候,再去执行查询的动作,执行AccountDao里面的findByUid方法 顺便把这个用户的uid数据给传递过去,这样就能够查询出来这个用户都有哪些账户了。 --> <collection property="accountList" fetchType="lazy" column="uid" select="com.zml.dao.AccountDao.findByUid"/> </resultMap> <!-- 懒加载的第一步: 查询第一张表的所有数据, 并且返回的类型不能使用resultType, 必须使用resultMap--> <select id="findAll" resultMap="userMap"> select * from t_user </select> </mapper>
AccountDao.java
public interface AccountDao { //懒加载的第二步: 根据uid来查询有哪些账户属于这个用户。 List<Account> findByUid(int uid); }
AccountDao.xml
<mapper namespace="com.zml.dao.AccountDao"> <!--这里是懒加载的第二步,所以只要查询即可,然后可以使用resultType--> <select id="findByUid" parameterType="int" resultType="account"> select * from t_account where uid = #{uid} </select> </mapper>
测试类
public class TestUserDao { @Test public void testFindAll(){ SqlSession session = SqlSessionFactoryUtil.getSession(); UserDao userDao = session.getMapper(UserDao.class); //查询所有的用户 List<User> list = userDao.findAll(); session.close(); //再打印list集合 System.out.println("list=" + list); } }
MyBatis注解开发
Mybatis 也可以使用注解开发方式,这样我们就可以减少编写 Mapper映射文件了。
使用 Mybatis 注解实现基本CRUD
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@SelectKey:保存之后 获得保存的id
public interface UserDao { //新增用户 @Insert("insert into t_user values(null,#{username},#{sex},#{birthday},#{address})") int add(User user ); //新增用户 @SelectKey(keyProperty = "uid" , resultType = int.class , before = false, statement = "select LAST_INSERT_ID()") @Insert("insert into t_user values(null,#{username},#{sex},#{birthday},#{address})") int add02(User user ); //删除用户 @Delete("delete from t_user where uid = #{uid}") int delete(int uid); //根据id找用户 @Select("select * from t_user where uid = #{uid}") User findByUid(int uid); //修改用户 @Update("update t_user set username = #{username}, sex=#{sex}, " + " birthday=#{birthday},address=#{address} where uid = #{uid}") int update(User user); //查询所有 @Select("select * from t_user") List<User> findAll(); }
SqlMapConfig.xml
<!-- 把当前包下的所有接口映射进行配置 --> <mappers> <!-- 批量配置 --> <package name="com.zml.dao"></package> </mappers>
使用Mybatis注解实现复杂关系映射开发
实现复杂关系映射之前我们可以在映射文件中通过配置<resultMap>来实现, @ResultMap 这个注解不是封装用的。
@Results 注解 , 代替的是标签<resultMap>
//该注解中可以使用单个@Result 注解,也可以使用@Result 集合 @Results({@Result(), @Result() })或@Results(@Result())
@Resutl 注解 ,代替了 <id>标签和<result>标签
@Result(column="列名",property="属性名",one=@One(select="指定用来多表查询的 sqlmapper"),many=@Many(select="")) @Resutl 注解属性说明 column 数据库的列名 Property 需要装配的属性名 one 需要使用的@One 注解(@Result(one=@One)())) many 需要使用的@Many 注解(@Result(many=@many)()))
@One 注解(一对一),代替了<assocation>标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。
@Result(column="列名",property="属性名",one=@One(select="指定用来多表查询的 sqlmapper"))
@Many 注解(一对多) ,代替了<Collection>标签,是是多表查询的关键,在注解中用来指定子查询返回对象集合
注意:聚集元素用来处理“一对多”的关系。需要指定映射的 Java 实体类的属性,属性的 javaType(一般为 ArrayList) 但是注解中可以不定义;
@Result(property="",column="",many=@Many(select=""))
使用注解实现(多)一对一复杂关系映射及延迟加载
查询账户(Account)信息并且关联查询用户(User)信息。
先查询账户(Account)信息,当我们需要用到用户(User)信息时再查询用户(User)信息。
User.java
@Data @NoArgsConstructor @AllArgsConstructor public class User implements Serializable { private int uid; private String username; private String sex; private Date birthday; private String address; }
Account.java
@Data @NoArgsConstructor @AllArgsConstructor public class Account { private Integer aid; private Integer uid; private Double money; //表示账户和用户的关系,一个账户只能属于一个用户 private User user; }
AccountDao.java
public interface AccountDao { //查询所有的账户,并且把账户属于的用户信息也查询出来 //使用注解实现的话,它的套路就是按照懒加载的写法来做。 //第一步:查询所有的账户 @Results(value={ @Result(column = "aid" , property = "aid",id = true), @Result(column = "uid" , property = "uid"), @Result(column = "money", property = "money"), @Result(property = "user" , column = "uid" , one = @One(fetchType = FetchType.DEFAULT, select = "com.zml.dao.UserDao.findByUid")) }) @Select("select * from t_account") List<Account> findAll(); }
UserDao.java
public interface UserDao { //第二步: 根据uid来找人 @Select("select * from t_user where uid = #{uid}") User findByUid(int uid); }
测试
public class TestAccountDao { @Test public void testFindAll(){ SqlSession session = SqlSessionFactoryUtil.getSession(); AccountDao accountDao = session.getMapper(AccountDao.class); List<Account> list = accountDao.findAll(); //System.out.println("list=" + list); session.close(); } }
使用注解实现一对多复杂关系映射及延迟加载
完成加载用户对象时,查询该用户所拥有的账户信息。
等账户信息使用的时候再查询.
Account.java
@Data @NoArgsConstructor @AllArgsConstructor public class Account { private Integer aid; private Integer uid; private Double money; }
User.java
@Data @NoArgsConstructor @AllArgsConstructor public class User implements Serializable { private int uid; private String username; private String sex; private Date birthday; private String address; //表示一个用户有很多的账户,通常使用List集合来表示 private List<Account> accountList; }
UserDao.java
public interface UserDao { /** * 一对多的第一步: 查询所有的用户 * 1. 这里的代码和前面的一对一的代码不差多少,结构大体相同 * 2. 唯一要注意的地方就是现在是一对多了,所以里面必须要使用@Many注解 * 3. @Results 其实就是我们以前xml的resultMap标签 * 4. @Result 其实就是以前xml的 <id> 和 <result标签 * 5. User这个类里面有一个属性(accountList)比较特殊一些, 它的数据不是从用户表来的,而是从账户表查询得到的 * 所以需要去查询账户表 * 6. 怎么查询账户表,调用哪个方法,要不要传递参数,就看里面怎么写。 * property : accountList 这个属性,它是一个集合 * select : 表示调用哪个方法 * column:表示传递什么参数过去。 * fetchType : 是否是懒加载,没有要求说一定要用懒加载! */ @Results(value={ @Result(column = "uid" , property = "uid" ,id = true), @Result(column = "username",property = "username"), @Result(column = "sex",property = "sex"), @Result(column = "birthday",property = "birthday"), @Result(column = "address",property = "address"), @Result(property ="accountList",column = "uid", many = @Many(fetchType = FetchType.DEFAULT,select = "com.zml.dao.AccountDao.findByUid")) }) @Select("select * from t_user") List<User> findAll(); }
AccountDao.java
public interface AccountDao { //一对多查询的第二步:根据用户的id来查询账户的信息 @Select("select * from t_account where uid = #{uid}") List<Account> findByUid(int uid); }
测试
public class TestUserDao03 { @Test public void findAll(){ SqlSession session = SqlSessionFactoryUtil.getSession(); UserDao userDao = session.getMapper(UserDao.class); //查询所有的用户信息 List<User> list = userDao.findAll(); System.out.println("list="+list); session.close(); } }
分页处理
使用PageHelper实现分页效果
添加依赖
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.10</version> </dependency>
核心配置SqlMapConfig.xml文件添加插件
<!--配置--> <configuration> <!-- 引入properties文件--> <properties resource="db.properties"/> <!-- 开启redis二级缓存 默认就是开启的可以不进行设置--> <settings> <setting name="cacheEnabled" value = "true"/> </settings> <!-- 需要在mybatis的核心配置文件中,配置分页插件。 位于`environments` 的前面。 配置分页的插件其实就是配置拦截器。这个拦截器有什么作用呢? 它的作用是能够让我们在不侵入代码|源码的情况下实现分页的效果,也就是它会在底层的sql语句给我们追加 limit ? ,? --> <plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"> </plugin> </plugins> </configuration>
javabean
@Data @NoArgsConstructor @AllArgsConstructor public class User implements Serializable { private int uid; private String username; private String sex; private Date birthday; private String address; }
dao
返回值使用pagehelper提供的 Page
最终的sql语句使用查询所有的语句
//测试分页 public interface UserDao04 { @Select("select * from t_user") List<User> findByPage(); //返回的数据类型应该是 Page 而不是List集合 @Select("select * from t_user") Page<User> findByPage02(); }
测试代码
在查询之前需要设置查询的参数。
public class TestUserDao { //分页查询 @Test public void testFindByPage(){ SqlSession session = SqlSessionFactoryUtil.getSession(); UserDao userDao = session.getMapper(UserDao.class); /* 在查询之前,先设置,想要看第几页,每页想看多少条 要想让这句话生效,必须有一个前提:拦截器必须要设置! */ PageHelper.startPage(1, 2); //分页查询 List是接口 ,等号的右边具体是哪一种类型呢? //返回的类型是分页工具jar里面的一个类 Page! //List<User> list = userDao.findByPage(); //System.out.println("list=" + list); Page<User> page = userDao.findByPage(); System.out.println("当前是第几页:" + page.getPageNum()); System.out.println("总共多少页:" + page.getPages()); System.out.println("每页显示多少条:" + page.getPageSize()); System.out.println("总共多少条:" + page.getTotal()); System.out.println("当前这一页的集合数据:" + page.getResult()); //虽然打印这一页的集合数据有点乱, 但是不影响它的遍历 List<User> list = page.getResult(); for (User user : list) { System.out.println("user=" + user); } session.close(); } }