准备写一个系列文章,MyBatis作为JavaEE框架开发的入门技术,所以作为开篇吧。
后续会推出MyBatisPlus、Spring、SpringMVC、Oracle、Maven、ElasticSearch、SpringBoot、SpringCloud、RabbitMQ、Nginx...等等,涉及到常用的JavaEE开发技术。
系列文章是用来记录自己在实际开发中常用的技术和内容,也可以做回顾,有遗漏的地方可以留言,待我记录后及时补充。
才疏学浅,写的文章难免有出错的地方,还望指正,大家相互进步。:smile:
——现役码农 2019-12-17
它是我们软件开发中的一套解决方案,不同的框架解决的是不同的问题。
使用框架的好处:框架封装了很多的细节,使开发者可以使用极简的方式实现功能,大大提高开发效率。
MyBatis是解决持久层问题,SpringMVC是解决表现层问题。
框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种定义认为,框架是可被应用开发者定制的应用骨架。前者是从应用方面而后者是从目的方面给出的定义。
简而言之,框架其实就是某种应用的半成品,就是一组组件,供你选用完成你自己的系统。简单说就是使用别人搭好的舞台,你来做表演。而且,框架一般是成熟的,不断升级的软件。
框架一般处在低层应用平台(如 J2EE )和高层业务逻辑之间的中间层。
软件开发的分层重要性: 框架的重要性在于它实现了部分功能,并且能够很好的将低层应用平台和高层业务逻辑进行了缓和。为了实现软件工程中的“高内聚、低耦合”。把问题划分开来各个解决,易于控制,易于延展,易于分配资源。我们常见的MVC 软件设计思想就是很好的分层思想。
表现层:是用于展示数据的(SpringMVC)
业务层:是处理业务需求
持久层:是和数据库交互的(MyBatis)
常见的 JavaEE 开发框架:
1 、解决数据的持久化问题的框架——MyBatis MyBatis:作为持久层的框架,还有一个封装程度更高的框架就是Hibernate,但这个框架因为各种原因目前在国内的流行程度下降太多,现在公司开发也越来越少使用。目前使用 Spring Data 来实现数据持久化也是一种趋势。 2、解决 WEB层问题的MVC框架——SpringMVC 3 、解决技术整合问题——Spring,Spring是一个轻量级控制反转(IOC)和面向切面(AOP)的容器框架
MyBatis 是支持定制化SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。 MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
mybatis是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。
mybatis通过 xml 或注解的方式将要执行的各种statement配置起来,并通过java对象和statement 中sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。
采用 ORM 思想解决了实体和数据库映射的问题,对 jdbc进行了封装,屏蔽了 jdbc api 底层访问细节,使我们不用与 jdbc api 打交道,就可以完成对数据库的持久化操作。
为了我们能够更好掌握框架运行的内部过程,并且有更好的体验,下面我们将从自定义 Mybatis 框架开始来学习框架。此时我们将会体验框架从无到有的过程体验,也能够很好的综合前面阶段所学的基础。
用 jdbc 的原始方法(未经封装)实现了查询数据库表记录的操作:
准备数据
-- ---------------------------- -- Table structure for t_user -- ---------------------------- DROP TABLE IF EXISTS `t_user`; CREATE TABLE `t_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(32) NOT NULL, `password` varchar(32) NOT NULL, `birthday` date DEFAULT NULL, `sex` bit(1) DEFAULT NULL, `address` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of t_user -- ---------------------------- INSERT INTO `t_user` VALUES ('1', 'AA', '123456', '2019-12-13', '', '北京'); INSERT INTO `t_user` VALUES ('2', 'BB', '123456', '2019-12-10', '/0', '上海'); INSERT INTO `t_user` VALUES ('3', 'CC', '123456', '2019-12-04', '', '广州'); 复制代码
Java代码:
/** * 用 jdbc 的原始方法(未经封装)实现了查询数据库表记录的操作 */ public class JdbcDemo { public static void main(String[] args) { Connection conn = null; PreparedStatement ps = null; ResultSet resultSet = null; try { //1. 注册驱动 Class.forName("com.mysql.jdbc.Driver"); //2. 获取连接对象 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC", "root", "123456"); //3. 准备SQL语句 String sql = "select * from t_user"; //4. 获取预处理statement ps = conn.prepareStatement(sql); //5.执行SQL语句 resultSet = ps.executeQuery(); //6.处理结果集 while (resultSet.next()) { int id = resultSet.getInt("id"); String username = resultSet.getString("username"); String password = resultSet.getString("password"); Date birthday = resultSet.getDate("birthday"); boolean sex = resultSet.getBoolean("sex"); String address = resultSet.getString("address"); //System.out.println(id + "--" + username + "--" + password + "--" + birthday + "--" + sex + "--" + address); User user = new User(id, username, password, birthday, sex, address); System.out.println(user); } } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } finally { //7. 释放资源 if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); resultSet = null; } } if (ps != null) { try { ps.close(); } catch (SQLException e) { e.printStackTrace(); ps = null; } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); conn = null; } } } } } 复制代码
User实体类:
@Data @AllArgsConstructor @NoArgsConstructor public class User implements Serializable { private static final long serialVersionUID = -2661131928069328685L; private int id; private String username; private String password; private Date birthday; private boolean sex; private String address; } 复制代码
输出日志:
User(id=1, username=AA, password=123456, birthday=2019-12-13, sex=true, address=北京) User(id=2, username=BB, password=123456, birthday=2019-12-10, sex=false, address=上海) User(id=3, username=CC, password=123456, birthday=2019-12-04, sex=true, address=广州) 复制代码
补充:
Lombok使用, blog.csdn.net/ThinkWon/ar…
自动生成 serialVersionUID 的设置, www.cnblogs.com/godtrue/p/7…
为了使MySQL JDBC驱动程序的5.1.33版本与UTC时区配合使用,必须在连接字符串中明确指定serverTimezone
jdbc:mysql://localhost:3306/mybatis?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC 复制代码
它使用了ORM思想实现了结果集的封装,Object Relational Mappging 对象关系映射,简单的说,就是把数据库表和实体类及实体类的属性对应起来,让我们可以操作实体类就实现操作数据库表。
文档创建时间:2019-12-12
开发平台:Windows
JDK版本:JDK1.8.0_131
开发工具:IntelliJ IDEA 2019.3
IntelliJ IDEA 2019.3 (Ultimate Edition) Build #IU-193.5233.84, built on November 25, 2019
先放上完整的项目目录结构图:
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.18</version> </dependency> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.3</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> <scope>provided</scope> </dependency> 复制代码
-- ---------------------------- -- Table structure for t_user -- ---------------------------- DROP TABLE IF EXISTS `t_user`; CREATE TABLE `t_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(32) NOT NULL, `password` varchar(32) NOT NULL, `birthday` date DEFAULT NULL, `sex` bit(1) DEFAULT NULL, `address` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of t_user -- ---------------------------- INSERT INTO `t_user` VALUES ('1', 'AA', '123456', '2019-12-13', '', '北京'); INSERT INTO `t_user` VALUES ('2', 'BB', '123456', '2019-12-10', '/0', '上海'); INSERT INTO `t_user` VALUES ('3', 'CC', '123456', '2019-12-04', '', '广州'); 复制代码
@Data @AllArgsConstructor @NoArgsConstructor public class User implements Serializable { private static final long serialVersionUID = -2661131928069328685L; private int id; private String username; private String password; private Date birthday; private boolean sex; private String address; } 复制代码
public interface IUserDao { /** * 查询所有 * * @return 返回用户集合 */ List<User> findAll(); } 复制代码
<?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"> <!--mybatis主配置文件--> <configuration> <!--配置环境--> <environments default="development"> <!--配置mysql的环境--> <environment id="development"> <!--配置事务的类型--> <transactionManager type="JDBC"/> <!--配置数据源(连接池)--> <dataSource type="POOLED"> <!--配置连接数据库的4个基本信息--> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <!--指定映射配置文件的位置,映射配置文件指的是每一个dao独立的配置文件--> <mappers> <mapper resource="com/ivyzh/dao/IUserMapper.xml"/> </mappers> </configuration> 复制代码
<?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.ivyzh.dao.IUserDao"> <select id="findAll" resultType="com.ivyzh.domain.User"> select * from t_user </select> </mapper> 复制代码
/** * MyBatis快速入门 */ public class MyBatisQuickStart { public static void main(String[] args) throws Exception { //1. 读取配置文件 InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml"); //2. 创建SqlSessionFactory工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(is); //3. 使用工厂生产SqlSession对象 SqlSession sqlSession = factory.openSession(); //4. 使用SqlSession创建Dao接口的代理对象 IUserDao userMapper = sqlSession.getMapper(IUserDao.class); //5. 使用代理对象执行方法 List<User> users = userMapper.findAll(); for (User user : users) { System.out.println(user); } //6. 释放资源 sqlSession.close(); is.close(); } } 复制代码
User(id=1, username=AA, password=123456, birthday=Fri Dec 13 08:00:00 CST 2019, sex=true, address=北京) User(id=2, username=BB, password=123456, birthday=Tue Dec 10 08:00:00 CST 2019, sex=false, address=上海) User(id=3, username=CC, password=123456, birthday=Wed Dec 04 08:00:00 CST 2019, sex=true, address=广州) 复制代码
环境搭建的注意事项:
第一个:创建IUserDao.xml (IUserMapper.xml)和 IUserDao.java时名称是为了和我们之前的知识保持一致。
在Mybatis中它把持久层的操作接口名称和映射文件也叫做:Mapper
所以:IUserDao 和 IUserMapper是一样的
第二个:在idea中创建目录的时候,它和包是不一样的
包在创建时:com.ivyzh.dao它是三级结构
目录在创建时:com.ivyzh.dao是一级目录
第三个:mybatis的映射配置文件位置必须和dao接口的包结构相同
第四个:映射配置文件的mapper标签 namespace属性的取值必须是dao接口的全限定类名
第五个:映射配置文件的操作配置(select), id属性的取值必须是dao接口的方法名
当我们遵从了第三,四,五点之后,我们在开发中就无须再写dao的实现类。
通过快速入门示例,我们发现使用 mybatis 是非常容易的一件事情,因为只需要编写 Dao 接口 并且按照mybatis 要求编写 两个配置文件(SqlMapCofig.xml和IUserMapper.xml) ,就可以实现功能。远比我们之前的 jdbc 方便多了。(我们使用注解之后,将变得更为简单,只需要编写一个 mybatis 配置文件(SqlMapCofig.xml)就够了。)
但是,这里面包含了许多细节,比如为什么会有工厂对象(SqlSessionFactory),为什么有了工厂之后还要有构建者对象(SqlSessionFactoryBuilder),为什么 IUserDao.xml 在创建时有位置和文件名的要求等等。
这些问题我们在自定义 mybatis 框架的章节,通过层层剥离的方式,给大家讲解。
请注意:我们讲解自定义 Mybatis 框架,不是让大家回去自己去写个 mybatis,而是让我们能更好的了解mybatis 内部是怎么执行的,在以后的开发中能更好的使用 mybatis 框架,同时对它的设计理念(设计模式)有一个认识。
注:为了演示使用注解版,我们换操作另外一张表t_student,里面的数据和结构和t_user是一模一样的。
先放目录结构和用到的类:
@Data @AllArgsConstructor @NoArgsConstructor public class Student implements Serializable { private static final long serialVersionUID = -4227290463847762031L; private int id; private String username; private String password; private Date birthday; private boolean sex; private String address; } 复制代码
public interface IStudentDao { /** * 查询所有 * * @return 返回学生集合 */ @Select("select * from t_student") List<Student> findAll(); } 复制代码
如果使用的注解来配置的话,此处应该使用class属性指定被注解的dao全限定类名:
<?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"> <!--mybatis主配置文件--> <configuration> <!--配置环境--> <environments default="development"> <!--配置mysql的环境--> <environment id="development"> <!--配置事务的类型--> <transactionManager type="JDBC"/> <!--配置数据源(连接池)--> <dataSource type="POOLED"> <!--配置连接数据库的4个基本信息--> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <!--指定映射配置文件的位置,映射配置文件指的是每一个dao独立的配置文件--> <mappers> <mapper resource="com/ivyzh/dao/IUserMapper.xml"/> <!--如果使用的注解来配置的话,此处应该使用class属性指定被注解的dao全限定类名--> <mapper class="com.ivyzh.dao.IStudentDao"/> </mappers> </configuration> 复制代码
使用注解方式,没有这个步骤。
/** * MyBatis快速入门-注解形式 */ public class StudentTest { public static void main(String[] args) throws Exception { //1. 读取配置文件 InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml"); //2. 创建SqlSessionFactory工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(is); //3. 使用工厂生产SqlSession对象 SqlSession sqlSession = factory.openSession(); //4. 使用SqlSession创建Dao接口的代理对象 IStudentDao studentMapper = sqlSession.getMapper(IStudentDao.class); //5. 使用代理对象执行方法 List<Student> students = studentMapper.findAll(); for (Student student : students) { System.out.println(student); } //6. 释放资源 sqlSession.close(); is.close(); } } 复制代码
Student(id=1, username=AA, password=123456, birthday=Fri Dec 13 08:00:00 CST 2019, sex=true, address=北京) Student(id=2, username=BB, password=123456, birthday=Tue Dec 10 08:00:00 CST 2019, sex=false, address=上海) Student(id=3, username=CC, password=123456, birthday=Wed Dec 04 08:00:00 CST 2019, sex=true, address=广州) 复制代码
常用MyBatis的CRUD方式XML和注解都已经介绍过了,还有一种是dao实现类方式实现CRUD,这种方式不常用,只是证明有这种方式,下面简单演示一下,就以User案例。
public class UserDaoImpl implements IUserDao { private SqlSessionFactory factory; public UserDaoImpl(SqlSessionFactory factory) { this.factory = factory; } @Override public List<User> findAll() { //使用工厂创建SqlSession对象 SqlSession sqlSession = factory.openSession(); //使用sqlSession执行查询所有方法 List<User> users = sqlSession.selectList("com.ivyzh.dao.IUserDao.findAll"); sqlSession.close(); //返回结果集 return users; } } 复制代码
/** * MyBatis快速入门-Dao实现类形式 */ public class UserImplTest { public static void main(String[] args) throws Exception { //1. 读取配置文件 InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml"); //2. 创建SqlSessionFactory工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(is); //3. 创建IUserDao实现类对象 IUserDao userDao = new UserDaoImpl(factory); //4. 调用查询方法 List<User> users = userDao.findAll(); for (User user : users) { System.out.println(user); } //5. 释放资源 is.close(); } } 复制代码
User(id=1, username=AA, password=123456, birthday=Fri Dec 13 08:00:00 CST 2019, sex=true, address=北京) User(id=2, username=BB, password=123456, birthday=Tue Dec 10 08:00:00 CST 2019, sex=false, address=上海) User(id=3, username=CC, password=123456, birthday=Wed Dec 04 08:00:00 CST 2019, sex=true, address=广州) 复制代码
上面的3种实现方式(XML、注解、dao实现)只是实现了findAll方法,接下来我们要真正实现增删改查功能了,是基于XML形式的,使用的表是Teacher了,数据格式和之前是User、Student是一模一样的,用户名改成TAA、TBB、TCC吧。
实现步骤就不具体列举了,直接贴代码
Teacher实体类:
@Data @AllArgsConstructor @NoArgsConstructor public class Teacher implements Serializable { private static final long serialVersionUID = -4399789553021464706L; private int id; private String username; private String password; private Date birthday; private boolean sex; private String address; } 复制代码
ITeacherDao实现类:
public interface ITeacherDao { /** * 查询所有 * * @return 返回老师集合 */ List<Teacher> findAll(); /** * 保存操作 * * @param teacher 老师数据 */ void saveTeacher(Teacher teacher); /** * 保存操作:问题拓展,新增用户id的返回 * * @param teacher 老师数据 */ void saveTeacher2(Teacher teacher); /** * 更新操作 * * @param teacher 老师数据 */ void updateTeacher(Teacher teacher); /** * 删除操作 * * @param id 编号id */ void deleteTeacher(Integer id); /** * 查询一个,根据id查询 * * @param id 用户id * @return 用户数据 */ Teacher findById(Integer id); /** * 模糊查询,根据用户名查询 * * @param username 用户名 * @return 用户数据列表 */ List<Teacher> findByName(String username); /** * 模糊查询,根据用户名查询,方式2 * * @param username 用户名 * @return 用户数据列表 */ List<Teacher> findByName2(String username); /** * 获取总记录数 * * @return 总记录数 */ Integer findTotal(); } 复制代码
ITeacherMapper.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.ivyzh.dao.ITeacherDao"> <!--查询所有--> <select id="findAll" resultType="com.ivyzh.domain.Teacher"> select * from t_teacher </select> <!--保存操作--> <insert id="saveTeacher" parameterType="com.ivyzh.domain.Teacher"> insert into t_teacher(username,password,birthday,sex,address) values (#{username},#{password},#{birthday},#{sex},#{address}) </insert> <!--保存操作,问题拓展,新增用户id的返回--> <insert id="saveTeacher2" parameterType="com.ivyzh.domain.Teacher"> /*keyProperty:属性名,keyColumn数据库中字段名*/ <selectKey keyProperty="id" keyColumn="id" resultType="java.lang.Integer" order="AFTER"> select last_insert_id(); </selectKey> insert into t_teacher(username,password,birthday,sex,address) values (#{username},#{password},#{birthday},#{sex},#{address}) </insert> <!--更新操作--> <update id="updateTeacher" parameterType="com.ivyzh.domain.Teacher"> update t_teacher set username=#{username},password=#{password},birthday=#{birthday},sex=#{sex},address=#{address} where id = #{id} </update> <!--删除操作,只有一个参数的时候mapper可以随便写,这是一个占位符--> <delete id="deleteTeacher" parameterType="java.lang.Integer"> delete from t_teacher where id=#{uid} </delete> <!--查询一个,根据id查询--> <select id="findById" parameterType="java.lang.Integer" resultType="com.ivyzh.domain.Teacher"> select * from t_teacher where id=#{id} </select> <!--模糊查询方式1--> <select id="findByName" parameterType="java.lang.String" resultType="com.ivyzh.domain.Teacher"> select * from t_teacher where username like #{username} </select> <!--模糊查询方式2,了解这种写法就好,实际开发中,不要用value--> <select id="findByName2" parameterType="java.lang.String" resultType="com.ivyzh.domain.Teacher"> select * from t_teacher where username like '%${value}%' </select> <!--mybatis也支持聚合函数,支持返回一行一列,获取总记录数--> <select id="findTotal" resultType="java.lang.Integer"> select count(id) from t_teacher </select> </mapper> 复制代码
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"> <!--mybatis主配置文件--> <configuration> <!--配置环境--> <environments default="development"> <!--配置mysql的环境--> <environment id="development"> <!--配置事务的类型--> <transactionManager type="JDBC"/> <!--配置数据源(连接池)--> <dataSource type="POOLED"> <!--配置连接数据库的4个基本信息--> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <!--指定映射配置文件的位置,映射配置文件指的是每一个dao独立的配置文件--> <mappers> <mapper resource="com/ivyzh/dao/IUserMapper.xml"/> <mapper resource="com/ivyzh/dao/ITeacherMapper.xml"/> <!--如果使用的注解来配置的话,此处应该使用class属性指定被注解的dao全限定类名--> <mapper class="com.ivyzh.dao.IStudentDao"/> </mappers> </configuration> 复制代码
以上需要注意的地方有: findByName2 、 saveTeacher2 两个方法,分别演示了模糊查询和新增用户id的返回问题。
parameterType类型:3种
基础类型,如查询一个,parameterType="java.lang.Integer"
<!--查询一个,根据id查询--> <select id="findById" parameterType="java.lang.Integer" resultType="com.ivyzh.domain.Teacher"> select * from t_teacher where id=#{id} </select> 复制代码
传递pojo对象:如更新操作,传递的就是Teacher对象
<!--更新操作--> <update id="updateTeacher" parameterType="com.ivyzh.domain.Teacher"> update t_teacher set username=#{username},password=#{password},birthday=#{birthday},sex=#{sex},address=#{address} where id = #{id} </update> 复制代码
传递pojo对象:如根据用户名查询用户信息,查询条件放到QueryVo的teacher属性中
<!--根据queryVo的条件查询用户--> <select id="findByVo" parameterType="com.ivyzh.domain.QueryVo" resultType="com.ivyzh.domain.Teacher"> select * from t_teacher where username like #{teacher.username} </select> 复制代码
OGNL表达式:(Apache)
Object Graphic Navigation Language
对象 图 导航 语言
它是通过对象的取值方法来获取数据。在写法上把get给省略了。
比如:我们获取用户的名称
类中的写法:user.getUsername();
OGNL表达式写法:user.username
mybatis中为什么能直接写username,而不用user.呢:
因为在parameterType中已经提供了属性所属的类,所以此时不需要写对象名
QueryVo类,放在了 package com.ivyzh.domain
包下面
@Data @AllArgsConstructor @NoArgsConstructor public class QueryVo { private Teacher teacher; } 复制代码
ITeacherMapp.xml
</mapper> .... <!--根据queryVo的条件查询用户--> <select id="findByVo" parameterType="com.ivyzh.domain.QueryVo" resultType="com.ivyzh.domain.Teacher"> select * from t_teacher where username like #{teacher.username} </select> </mapper> 复制代码
TeacherTest.java测试类方法:
@Test public void testFindByVo() { ITeacherDao mapper = sqlSession.getMapper(ITeacherDao.class); QueryVo vo = new QueryVo(); Teacher teacher = new Teacher(); teacher.setUsername("%zs%"); vo.setTeacher(teacher); List<Teacher> teachers = mapper.findByVo(vo); for (Teacher t : teachers) { System.out.println(t); } } 复制代码
输出日志:
Teacher(id=10, username=zs1, password=123456, birthday=Sat Dec 14 08:00:00 CST 2019, sex=false, address=杭州) Teacher(id=12, username=zs2, password=123456, birthday=Sat Dec 14 08:00:00 CST 2019, sex=false, address=杭州) Teacher(id=13, username=zs3, password=123456, birthday=Sat Dec 14 08:00:00 CST 2019, sex=false, address=杭州) 复制代码
场景解释:就是数据库字段和自定义的实体类的属性名不是一一对应,那会是怎样呢?
为了演示这样一个问题,我们copy一份t_teacher数据,然后创建t_worker数据库来演示,里面的内容结构和之前的都一样,如下:
接下来就是copy之前的Worker实体类、IWorkerDao接口、Mapper、测试类、SqlMapperConfig
Teacher的实体类是这样的,是和数据库字段一样的,
@Data @AllArgsConstructor @NoArgsConstructor public class Teacher implements Serializable { private static final long serialVersionUID = -4399789553021464706L; private int id; private String username; private String password; private Date birthday; private boolean sex; private String address; } 复制代码
我们做一下简单的修改,每个字段前面加一个user:
@Data @AllArgsConstructor @NoArgsConstructor public class Worker implements Serializable { private static final long serialVersionUID = -4399789553021464706L; private int userId; private String userName; private String userPassword; private Date userBirthday; private boolean userSex; private String userAddress; } 复制代码
public interface IWorkerDao { /** * 查询所有 * * @return 返回老师集合 */ List<Worker> findAll(); /** * 保存操作 * * @param worker 老师数据 */ void saveWorker(Worker worker); /** * 保存操作:问题拓展,新增用户id的返回 * * @param worker 老师数据 */ void saveWorker2(Worker worker); /** * 更新操作 * * @param worker 老师数据 */ void updateWorker(Worker worker); /** * 删除操作 * * @param id 编号id */ void deleteWorker(Integer id); /** * 查询一个,根据id查询 * * @param id 用户id * @return 用户数据 */ Worker findById(Integer id); /** * 模糊查询,根据用户名查询 * * @param username 用户名 * @return 用户数据列表 */ List<Worker> findByName(String username); /** * 模糊查询,根据用户名查询,方式2 * * @param username 用户名 * @return 用户数据列表 */ List<Worker> findByName2(String username); /** * 获取总记录数 * * @return 总记录数 */ Integer findTotal(); } 复制代码
<?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.ivyzh.dao.IWorkerDao"> <!--查询所有--> <select id="findAll" resultType="com.ivyzh.domain.Worker"> select * from t_worker </select> <!--保存操作--> <insert id="saveWorker" parameterType="com.ivyzh.domain.Worker"> insert into t_worker(username,password,birthday,sex,address) values (#{userName},#{userPassword},#{userBirthday},#{userSex},#{userAddress}) </insert> <!--保存操作,问题拓展,新增用户id的返回--> <insert id="saveWorker2" parameterType="com.ivyzh.domain.Worker"> /*keyProperty:属性名,keyColumn数据库中字段名*/ <selectKey keyProperty="userId" keyColumn="id" resultType="java.lang.Integer" order="AFTER"> select last_insert_id(); </selectKey> insert into t_worker(username,password,birthday,sex,address) values (#{userName},#{userPassword},#{userBirthday},#{userSex},#{userAddress}) </insert> <!--更新操作--> <update id="updateWorker" parameterType="com.ivyzh.domain.Worker"> update t_worker set username=#{userName},password=#{userPassword},birthday=#{userBirthday},sex=#{userSex},address=#{userAddress} where id = #{userId} </update> <!--删除操作,只有一个参数的时候mapper可以随便写,这是一个占位符--> <delete id="deleteWorker" parameterType="java.lang.Integer"> delete from t_worker where id=#{uid} </delete> <!--查询一个,根据id查询--> <select id="findById" parameterType="java.lang.Integer" resultType="com.ivyzh.domain.Worker"> select * from t_worker where id=#{id} </select> <!--模糊查询方式1--> <select id="findByName" parameterType="java.lang.String" resultType="com.ivyzh.domain.Worker"> select * from t_worker where username like #{userName} </select> <!--模糊查询方式2,了解这种写法就好,实际开发中,不要用value--> <select id="findByName2" parameterType="java.lang.String" resultType="com.ivyzh.domain.Worker"> select * from t_worker where username like '%${value}%' </select> <!--mybatis也支持聚合函数,支持返回一行一列,获取总记录数--> <select id="findTotal" resultType="java.lang.Integer"> select count(id) from t_worker </select> </mapper> 复制代码
注意这里的findAll、findById、findByName、findByName2这几个方法如果不修改是有问题的。
这里先简单说下情况就是调用findAll之后的方法,日志如下:
Worker(userId=0, userName=TAA, userPassword=null, userBirthday=null, userSex=false, userAddress=null) Worker(userId=0, userName=TBB, userPassword=null, userBirthday=null, userSex=false, userAddress=null) Worker(userId=0, userName=TCC, userPassword=null, userBirthday=null, userSex=false, userAddress=null) 复制代码
可以发现只有userName字段查出来了,为什么呢?因为mysql在windows环境下不区分大小写!
加入IWorkerMapper映射配置文件的位置,
<mappers> <mapper resource="com/ivyzh/dao/IUserMapper.xml"/> <mapper resource="com/ivyzh/dao/ITeacherMapper.xml"/> <mapper resource="com/ivyzh/dao/IWorkerMapper.xml"/> <!--如果使用的注解来配置的话,此处应该使用class属性指定被注解的dao全限定类名--> <mapper class="com.ivyzh.dao.IStudentDao"/> </mappers> 复制代码
/** * MyBatis快速入门-Worker基于XML形式的增删改查测试 */ public class WorkerTest { InputStream is; SqlSession sqlSession; @Before public void init() throws IOException { //1. 读取配置文件 is = Resources.getResourceAsStream("SqlMapConfig.xml"); //2. 创建SqlSessionFactory工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(is); //3. 使用工厂生产SqlSession对象 sqlSession = factory.openSession(); } /** * 查询所有用户数据 */ @Test public void testFindAll() { IWorkerDao mapper = sqlSession.getMapper(IWorkerDao.class); List<Worker> workers = mapper.findAll(); for (Worker worker : workers) { System.out.println(worker); } } /** * 保存操作 */ @Test public void testSave() { IWorkerDao mapper = sqlSession.getMapper(IWorkerDao.class); Worker t = new Worker(0, "zs14", "123456", new Date(), false, "杭州"); mapper.saveWorker(t); List<Worker> workers = mapper.findAll(); for (Worker worker : workers) { System.out.println(worker); } //提交事务,或者放到destory里面 //sqlSession.commit(); } /** * 保存操作 */ @Test public void testSave2() { IWorkerDao mapper = sqlSession.getMapper(IWorkerDao.class); Worker t = new Worker(0, "zs15", "123456", new Date(), false, "杭州"); System.out.println("保存操作之前:" + t); mapper.saveWorker2(t); System.out.println("保存操作之后:" + t); List<Worker> workers = mapper.findAll(); for (Worker worker : workers) { System.out.println(worker); } } @Test public void testUpdate() { IWorkerDao mapper = sqlSession.getMapper(IWorkerDao.class); Worker t = new Worker(13, "zs13", "", new Date(), true, "南京"); mapper.updateWorker(t); List<Worker> workers = mapper.findAll(); for (Worker worker : workers) { System.out.println(worker); } } @Test public void testDelete() { IWorkerDao mapper = sqlSession.getMapper(IWorkerDao.class); mapper.deleteWorker(6); List<Worker> workers = mapper.findAll(); for (Worker worker : workers) { System.out.println(worker); } } @Test public void testFindById() { IWorkerDao mapper = sqlSession.getMapper(IWorkerDao.class); Worker worker = mapper.findById(1); System.out.println(worker); } @Test public void testFindByName() { IWorkerDao mapper = sqlSession.getMapper(IWorkerDao.class); List<Worker> workers = mapper.findByName("%T%"); for (Worker worker : workers) { System.out.println(worker); } } @Test public void testFindByName2() { IWorkerDao mapper = sqlSession.getMapper(IWorkerDao.class); List<Worker> workers = mapper.findByName2("T"); for (Worker worker : workers) { System.out.println(worker); } } @Test public void testFindTotal() { IWorkerDao mapper = sqlSession.getMapper(IWorkerDao.class); Integer total = mapper.findTotal(); System.out.println("total -> " + total); } @After public void destory() throws IOException { //提交事务,或者放到destory里面 sqlSession.commit(); //6. 释放资源 sqlSession.close(); is.close(); } } 复制代码
可以发现在查询的时候,并没有将查询字段封装到Domain实体类里面,那么该怎么解决呢?这里有两个方案
起别名
这种方式是执行最快的,比如这里我只修改findAll方法如下:
<!--查询所有--> <select id="findAll" resultType="com.ivyzh.domain.Worker"> select id as userId,username as userName,password as userPassword,birthday as userBirthday,sex as userSex,address as userAddress from t_worker </select> 复制代码
运行结果:
Worker(userId=1, userName=TAA, userPassword=123456, userBirthday=Fri Dec 13 08:00:00 CST 2019, userSex=true, userAddress=北京) Worker(userId=2, userName=TBB, userPassword=123456, userBirthday=Tue Dec 10 08:00:00 CST 2019, userSex=false, userAddress=上海) Worker(userId=3, userName=TCC, userPassword=123456, userBirthday=Wed Dec 04 08:00:00 CST 2019, userSex=true, userAddress=广州) 复制代码
可以发现数据已经填充好了。
使用resultMap
即配置查询结果的列名和实体类的属性名对应关系
<mapper namespace="com.ivyzh.dao.IWorkerDao"> <!--配置查询结果的列名和实体类的属性名的对应关系--> <resultMap id="workerMap" type="com.ivyzh.domain.Worker"> <!--主键字段的对应,property:对应实体类,column:对应数据库列名--> <id property="userId" column="id"/> <!--非主键字段的对应--> <result property="userName" column="username"/> <result property="userPassword" column="password"/> <result property="userSex" column="sex"/> <result property="userAddress" column="address"/> </resultMap> ... <!--查询一个,根据id查询--> <select id="findById" parameterType="java.lang.Integer" resultMap="workerMap"> select * from t_worker where id=#{id} </select> <!--模糊查询方式1--> <select id="findByName" parameterType="java.lang.String" resultMap="workerMap"> select * from t_worker where username like #{userName} </select> <!--模糊查询方式2,了解这种写法就好,实际开发中,不要用value--> <select id="findByName2" parameterType="java.lang.String" resultMap="workerMap"> select * from t_worker where username like '%${value}%' </select> ... </mapper> 复制代码
这样调用findByName方法,就会打印如下内容:
Worker(userId=1, userName=TAA, userPassword=123456, userBirthday=null, userSex=true, userAddress=北京) Worker(userId=2, userName=TBB, userPassword=123456, userBirthday=null, userSex=false, userAddress=上海) Worker(userId=3, userName=TCC, userPassword=123456, userBirthday=null, userSex=true, userAddress=广州) 复制代码
两种方式对比:
第一种执行效率高,是在sql层面上修改的,起别名。但是sql语句该找了改造量大!对于开发效率比较低。
第二种方式因为多引入了xml文件,需要多一步xml解析。所以效率不如上面的高,但是封装好的resultMap多处可以复用!开发效率高!
再回顾一下,至此项目的目录:
依次用到了User、Student、Teacher、Worker实体类,为了演示不同的场景,才新建这么多测试类的,到时候回头来看,比较清晰。自己研究琢磨的时候,可以用一个实体类就行 :smile:
properties标签作用:用于抽取数据库连接配置信息。
我们现在将数据库连接池写在了 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"> <!--mybatis主配置文件--> <configuration> <!--配置环境--> <environments default="development"> <!--配置mysql的环境--> <environment id="development"> <!--配置事务的类型--> <transactionManager type="JDBC"/> <!--配置数据源(连接池)--> <dataSource type="POOLED"> <!--配置连接数据库的4个基本信息--> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <!--指定映射配置文件的位置,映射配置文件指的是每一个dao独立的配置文件--> <mappers> <mapper resource="com/ivyzh/dao/IUserMapper.xml"/> <mapper resource="com/ivyzh/dao/ITeacherMapper.xml"/> <mapper resource="com/ivyzh/dao/IWorkerMapper.xml"/> <!--如果使用的注解来配置的话,此处应该使用class属性指定被注解的dao全限定类名--> <mapper class="com.ivyzh.dao.IStudentDao"/> </mappers> </configuration> 复制代码
properties的功能就是将dataSource标签下面的数据库连接信息抽出来,为此我们可以有这样几种操作:
第一种:
<configuration> <!--配置properties--> <properties> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="123456"/> </properties> <!--配置环境--> <environments default="development"> <!--配置mysql的环境--> <environment id="development"> <!--配置事务的类型--> <transactionManager type="JDBC"/> <!--配置数据源(连接池)--> <dataSource type="POOLED"> <!--配置连接数据库的4个基本信息,使用properties标签--> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> ... </configuration> 复制代码
第二种:
使用properties的resource属性,指定配置文件位置。即可以在 properties
标签内部配置连接数据的信息,也可以通过属性引用外部配置文件信息。
resource属性:用于指定配置文件的位置,是按照类路径的写法来写,必须存在于类路径下。
jdbcConfig.properties的存放路径和内容:
引用的时候,可以这样配置:
<configuration> <!--配置properties--> <properties resource="jdbcConfig.properties"/> ... </configuration> 复制代码
第三种:
使用properties的url属性。
比如我把jdbcConfig.properties连接信息copy到D盘根目录,则引用的时候应该这样做:
<configuration> <!--配置properties--> <properties url="file:///D:/jdbcConfig.properties"/> ... </configuration> 复制代码
回顾IWorkerMapper.xml这个文件中有几个写的是类全限定名,如图:
在resultMap标签type属性、select标签的resultType、parameterType属性都写了全限定名 com.ivyzh.domain.Worker
这时候typeAliases出来就是来优化这种写法的。
使用typeAliases配置别名,它只能配置domain中类的别名。
typeAlias的type属性指的是实体类全限定名,alias属性指起的别名,不区分大小写。
同样这个需要在SqlMapConfig.xml文件里面去配置:
使用的时候可以直接写别名了,且不区分大小写:
typeAliases配置过的时候也是很麻烦的,比如又N个实体类需要配置,那么就需要在 typeAliases
标签下面写N个 typeAlias
,所以它提供了packge标签。
配置如下:
<!--使用typeAliases配置别名,它只能配置domain中类的别名--> <typeAliases> <package name="com.ivyzh.domain"/> </typeAliases> 复制代码
这样表示在 com.ivyzh.domain
包下面的实体类都不用再一个个配置了,他们的默认别名就是类名。
同理:mappers标签下面也有package标签,是指定接口所在的包,当指定了之后就不需要写mapper、resources、class了。
比如我们用到的User、Student、Teacher、Worker实体类所对应的Mapper.xml或者Dao的实现类文件都要配置在mappers标签下面。
用了package标签之后,就简洁很多了。
<mappers> <!--pack标签是用于指定dao接口所在的包,当指定了之后就不需要再写mapper及resource或者class了--> <!--目前这个功能运行报错,不知道为什么?暂时记录一下--> <!--<package name="com.ivyzh.dao"/>--> </mappers> 复制代码
调用查询所有的报错信息:
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.ivyzh.dao.IWorkerDao.findAll 复制代码
最后解决了,参考文章https://blog.csdn.net/weixin_43951534/article/details/90416363
MyBatis注册映射文件,package 加载方式(class加载方式的简写方式),适用于类路径下,接口文件与映射文件在同一路径下,且 接口名与映射文件名相同 ,并且映射文件命名为接口全类名的情况
即IWorkerMapper.xml要改成IWorkerDao.xml才行!太坑了!
修改完之后的目录结构:
关于package、mappers几篇参考文章:
www.jianshu.com/p/4e50fa289…
blog.csdn.net/u014268482/…
blog.csdn.net/qq_31457665…
blog.csdn.net/weixin_4395…
这一节内容:原理部分了解,应用部分会用即可。
它可以减少我们获取连接所消耗的时间
mybatis连接池提供了3种方式的配置: 配置的位置:主配置文件SqlMapConfig.xml中的dataSource标签,type属性就是表示采用何种连接池方式,type属性的取值3种。
POOLED:采用传统的javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实现
UNPOOLED: 采用传统的获取连接的方式,虽然也实现Javax.sql.DataSource接口,但是并没有使用池的思想。
JNDI(拓展了解): 采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到DataSource是不一样。
注意:如果不是web或者maven的war工程,是不能使用的。tomcat服务器,采用连接池就是dbcp连接池。
mybatis中的事务,它是通过sqlsession对象的commit方法和rollback方法实现事务的提交和回滚。
如何设置代码让其自动提交?
sqlSession = sqlSessionFactory.openSession(boolean autoCommit);//设置true就可以了。
这一块,没有具体研究过,先暂存吧,后面找到合适例子再补充。
这一节内容:会用即可。
Mybatis 的映射文件中,前面我们的 SQL 都是比较简单的,有些时候业务逻辑复杂时,我们的 SQL 是动态变化的,此时在前面的学习中我们的 SQL 就不能满足要求了。
再新建一个厨师表 t_chef
数据和 t_worker
一模一样。
@Data @AllArgsConstructor @NoArgsConstructor public class Chef implements Serializable { private static final long serialVersionUID = -6815279398228636657L; private int id; private String username; private String password; private Date birthday; private Boolean sex; private String address; } 复制代码
注意这里sex是Boolen类型。
public interface IChefDao { /** * 查询所有 * * @return 返回老师集合 */ List<Chef> findAll(); /** * 根据条件查询,查询内容可能是姓名、性别、地址... * 演示if标签 * * @param chef 用户数据 * @return 用户数据列表 */ List<Chef> findByCondition(Chef chef); /** * 根据条件查询,查询内容可能是姓名、性别、地址... * 演示where标签 * * @param chef 用户数据 * @return 用户数据列表 */ List<Chef> findByCondition2(Chef chef); /** * 根据ChefQueryVo中提供的id集合,查询用户信息 * * @param vo 查询条件 * @return 用户数据列表 */ List<Chef> findByIds(ChefQueryVo vo); } 复制代码
@Data @AllArgsConstructor @NoArgsConstructor public class ChefQueryVo { private List<Integer> ids; } 复制代码
注:由于在SQLMapConfig文件使用package标签,则在定义Mapper映射文件的时候要和接口名称定义成相同的,IChefDao.xml
/** * MyBatis快速入门-ChefTest基于XML形式的增删改查测试 */ public class ChefTest { InputStream is; SqlSession sqlSession; @Before public void init() throws IOException { //1. 读取配置文件 is = Resources.getResourceAsStream("SqlMapConfig.xml"); //2. 创建SqlSessionFactory工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(is); //3. 使用工厂生产SqlSession对象 sqlSession = factory.openSession(); } /** * 查询所有用户数据 */ @Test public void testFindAll() { IChefDao mapper = sqlSession.getMapper(IChefDao.class); List<Chef> chefs = mapper.findAll(); print(chefs); } private void print(List<Chef> chefs) { for (Chef chef : chefs) { System.out.println(chef); } } @Test public void testFindByCondition() { IChefDao mapper = sqlSession.getMapper(IChefDao.class); Chef chef = new Chef(); chef.setUsername("TAA"); List<Chef> chefs = mapper.findByCondition(chef); print(chefs); } @Test public void testFindByCondition2() { IChefDao mapper = sqlSession.getMapper(IChefDao.class); Chef chef = new Chef(); chef.setAddress("杭州"); List<Chef> chefs = mapper.findByCondition2(chef); print(chefs); } @Test public void testFindByQueryVo() { IChefDao mapper = sqlSession.getMapper(IChefDao.class); ChefQueryVo vo = new ChefQueryVo(); ArrayList<Integer> ids = new ArrayList<>(); ids.add(1); ids.add(3); ids.add(5); ids.add(8); vo.setIds(ids); List<Chef> chefs = mapper.findByIds(vo); print(chefs); } @After public void destory() throws IOException { //提交事务,或者放到destory里面 sqlSession.commit(); //6. 释放资源 sqlSession.close(); is.close(); } } 复制代码
sql片段意思是抽取重复语句,如:
<mapper namespace="com.ivyzh.dao.IChefDao"> <!--抽取sql代码块--> <sql id="selectAll"> select * from t_chef </sql> <!--查询所有--> <select id="findAll" resultType="chef"> <include refid="selectAll"/> </select> ... <mapper/> 复制代码
<mapper namespace="com.ivyzh.dao.IChefDao"> <!--抽取sql代码块--> <sql id="selectAll"> select * from t_chef </sql> <!--查询所有--> <select id="findAll" resultType="chef"> <include refid="selectAll"/> </select> <!--根据条件查询,查询内容可能是姓名、性别、地址...--> <select id="findByCondition" parameterType="chef" resultType="chef"> select * from t_chef where 1 = 1 <if test="username != null"> and username = #{username} </if> <if test="sex != null"> and sex = #{sex} </if> <if test="address != null"> and address = #{address} </if> </select> ... <mapper/> 复制代码
if标签需要手写 where 1 = 1
,使用where标签我们可以这样做:
... <!--根据条件查询,查询内容可能是姓名、性别、地址...演示where标签--> <select id="findByCondition2" parameterType="chef" resultType="chef"> select * from t_chef <where> <if test="username != null"> and username = #{username} </if> <if test="sex != null"> and sex = #{sex} </if> <if test="address != null"> and address = #{address} </if> </where> </select> ... 复制代码
... <!--根据ChefQueryVo中提供的id集合,查询用户信息--> <!--注意size()可以写成size--> <select id="findByIds" parameterType="queryvo" resultType="chef"> <include refid="selectAll"/> <where> <if test="ids != null and ids.size() >0"> <foreach collection="ids" open="and id in (" close=")" item="uid" separator=","> #{uid} </foreach> </if> </where> </select> ... 复制代码
p6spy是一个开源项目,通常使用它来跟踪数据库操作,查看程序运行过程中执行的sql语句。
参考文章: blog.csdn.net/gnd15732625…
其实上面这种方式在实际开发中并不常用,应用场景举例,一个用户可以有多个银行账户,一个银行账户唯一对应一个用户。
根据账户id查询用户信息,就是一个一对一的案例。实现起来不难,可以单表查询就行,这里我们直接演示另一种情况,一对多。后面还会提到一对一查询,详见 延迟加载-一对一实现立即加载章节 。
一对多情况:一个用户可以有多张银行卡,场景是根据用户id查询他的用户信息和银行卡信息。
创建客户表 t_account
:
DROP TABLE IF EXISTS t_account; CREATE TABLE t_account ( id INT(10) PRIMARY KEY NOT NULL AUTO_INCREMENT, money INT(10), uid INT(10) NOT NULL ); 复制代码
创建客户表 t_customer
:
表结构和数据和之前的Worker一样。
t_customer
的数据内容:
t_account
和 t_customer
建立外键约束:
t_account
表数据:
先验证一下sql查询:
查询用户和其对应的账户信息
SELECT * FROM t_customer,t_account WHERE t_customer.id = t_account. uid
查询用户id为2的用户和其对应的账户信息
SELECT * FROM t_customer c,t_account a WHERE c.id = a. uid
AND c.id =1
账户表Account:
@Data @AllArgsConstructor @NoArgsConstructor public class Account implements Serializable { private static final long serialVersionUID = -5255167209921667740L; private Integer id; private Integer money; private Integer uid; } 复制代码
顾客表Customer:
@Data @AllArgsConstructor @NoArgsConstructor public class Customer implements Serializable { private static final long serialVersionUID = 3456456284746844493L; private Integer id; private String username; private String password; private Date birthday; private Boolean sex; private String address; /** * 一对多关系映射:主表实体类中应该包含从表实体的集合引用 */ private List<Account> accounts; } 复制代码
顾客操作接口ICustomerDao:
public interface ICustomerDao { /** * 查询所有 * * @return 返回数据集合 */ List<Customer> findAll(); /** * 查询所有并包含账户信息 * * @return 返回数据集合 */ List<Customer> findAllWithAccount(); } 复制代码
ICustomerDao.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.ivyzh.dao.ICustomerDao"> <!--配置Customer的resultMap--> <resultMap id="caMap" type="customer"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="password" column="password"/> <result property="sex" column="sex"/> <result property="birthday" column="birthday"/> <result property="address" column="address"/> <!--配置customer对象中accounts集合的映射--> <collection property="accounts" ofType="account"> <id property="id" column="aid"/> <result property="money" column="money"/> <result property="uid" column="uid"/> </collection> </resultMap> <!--抽取sql代码块--> <sql id="selectAll"> select * from t_customer </sql> <!--查询所有--> <select id="findAll" resultType="customer"> <include refid="selectAll"/> </select> <!--查询所有并包含账户信息--> <select id="findAllWithAccount" resultMap="caMap"> select c.*,a.id as aid ,a.money money,a.uid uid from t_customer c left outer join t_account a on c.id = a.uid </select> </mapper> 复制代码
测试类CustomerTest:
/** * MyBatis快速入门-CustomerTest基于XML形式的增删改查测试 */ public class CustomerTest { InputStream is; SqlSession sqlSession; @Before public void init() throws IOException { //1. 读取配置文件 is = Resources.getResourceAsStream("SqlMapConfig.xml"); //2. 创建SqlSessionFactory工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(is); //3. 使用工厂生产SqlSession对象 sqlSession = factory.openSession(); } /** * 查询所有用户数据 */ @Test public void testFindAll() { ICustomerDao mapper = sqlSession.getMapper(ICustomerDao.class); List<Customer> customers = mapper.findAll(); print(customers); } @Test public void testFindAllWithAccount() { ICustomerDao mapper = sqlSession.getMapper(ICustomerDao.class); List<Customer> customers = mapper.findAllWithAccount(); print(customers); } private void print(List<Customer> objs) { for (Customer obj : objs) { System.out.println(obj); } } @After public void destory() throws IOException { //提交事务,或者放到destory里面 sqlSession.commit(); //6. 释放资源 sqlSession.close(); is.close(); } } 复制代码
输出结果:
testFindAll方法
Customer(id=1, username=TAA, password=123456, birthday=Fri Dec 13 08:00:00 CST 2019, sex=true, address=北京, accounts=null) Customer(id=2, username=TBB, password=123456, birthday=Tue Dec 10 08:00:00 CST 2019, sex=false, address=上海, accounts=null) Customer(id=3, username=TCC, password=123456, birthday=Wed Dec 04 08:00:00 CST 2019, sex=true, address=广州, accounts=null) 复制代码
testFindAllWithAccount方法
Customer(id=1, username=TAA, password=123456, birthday=Fri Dec 13 08:00:00 CST 2019, sex=true, address=北京, accounts=[Account(id=1, money=1000, uid=1), Account(id=2, money=2000, uid=1), Account(id=3, money=3000, uid=1)]) Customer(id=2, username=TBB, password=123456, birthday=Tue Dec 10 08:00:00 CST 2019, sex=false, address=上海, accounts=[Account(id=4, money=4000, uid=2), Account(id=5, money=5000, uid=2)]) Customer(id=3, username=TCC, password=123456, birthday=Wed Dec 04 08:00:00 CST 2019, sex=true, address=广州, accounts=[Account(id=6, money=6000, uid=3)]) 复制代码
注意事项:collection标签里面的书写格式。
示例:用户和角色
一个用户可以有多个角色
一个角色可以赋予多个用户
步骤:
1、建立两张表:用户表,角色表
让用户表和角色表具有多对多的关系。需要使用中间表,中间表中包含各自的主键,在中间表中是外键。
2、建立两个实体类:用户实体类和角色实体类
让用户和角色的实体类能体现出来多对多的关系
各自包含对方一个集合引用
3、建立两个配置文件
用户的配置文件
角色的配置文件
4、实现配置:
当我们查询用户时,可以同时得到用户所包含的角色信息
当我们查询角色时,可以同时得到角色的所赋予的用户信息
演示案例:当我们查询用户时,可以同时得到用户所包含的角色信息
t_role
角色表:
DROP TABLE IF EXISTS t_role; CREATE TABLE t_role ( id INT(10) PRIMARY KEY NOT NULL AUTO_INCREMENT, role_name VARCHAR(10), role_desc VARCHAR(10) ); 复制代码
t_employee
员工表:
结构和数据与 t_customer
表一致。
t_employee_role
角色权限对应表
DROP TABLE IF EXISTS t_employee_role; CREATE TABLE t_employee_role ( id INT(10) PRIMARY KEY NOT NULL AUTO_INCREMENT, cid INT(10) NOT NULL, rid INT(10) NOT NULL ); 复制代码
加入外键:
加一下数据:
t_employee:
t_role:
t_employee_role:
简单写一个sql查询一下:
SELECT e.*,r.id AS rid,r.role_name,r.role_desc FROM t_role r LEFT OUTER JOIN t_employee_role er ON r.id = er.rid LEFT OUTER JOIN t_employee e ON e.id = er.cid;
Employee:
@Data @AllArgsConstructor @NoArgsConstructor public class Employee implements Serializable { private static final long serialVersionUID = -2661131928069328685L; private Integer id; private String username; private String password; private Date birthday; private boolean sex; private String address; /** * 一对多关系映射:主表实体类中应该包含从表实体的集合引用 */ private List<Role> roles; } 复制代码
Role:
@Data @AllArgsConstructor @NoArgsConstructor public class Role implements Serializable { private static final long serialVersionUID = -2661131928069328685L; private Integer roleId; private String roleName; private String roleDesc; } 复制代码
注意这里我们故意将字段名和数据库存储字段写的不一样。
IEmployeeDao:
public interface IEmployeeDao { /** * 查询所有 * * @return 返回数据集合 */ List<Employee> findAll(); /** * 查询所有并包含角色信息 * * @return 返回数据集合 */ List<Employee> findAllWithRole(); } 复制代码
IEmployeeDao.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.ivyzh.dao.IEmployeeDao"> <!--配置Customer的resultMap--> <resultMap id="erMap" type="employee"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="password" column="password"/> <result property="sex" column="sex"/> <result property="birthday" column="birthday"/> <result property="address" column="address"/> <!--配置customer对象中accounts集合的映射--> <collection property="roles" ofType="role"> <id property="roleId" column="rid"/> <result property="roleName" column="role_name"/> <result property="roleDesc" column="role_desc"/> </collection> </resultMap> <!--抽取sql代码块--> <sql id="selectAll"> select * from t_employee </sql> <!--查询所有--> <select id="findAll" resultType="employee"> <include refid="selectAll"/> </select> <!--查询所有并包含账户信息--> <select id="findAllWithRole" resultMap="erMap"> SELECT e.*,r.id AS rid,r.`role_name`,r.`role_desc` FROM t_employee e LEFT OUTER JOIN t_employee_role er ON e.id = er.`cid` LEFT OUTER JOIN t_role r ON r.`id` = er.`rid` </select> </mapper> 复制代码
EmployeeTest:
/** * EmployeeTest演示多对多——一个雇员对应多个身份 */ public class EmployeeTest { InputStream is; SqlSession sqlSession; @Before public void init() throws IOException { //1. 读取配置文件 is = Resources.getResourceAsStream("SqlMapConfig.xml"); //2. 创建SqlSessionFactory工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(is); //3. 使用工厂生产SqlSession对象 sqlSession = factory.openSession(); } /** * 查询所有用户数据 */ @Test public void testFindAll() { IEmployeeDao mapper = sqlSession.getMapper(IEmployeeDao.class); List<Employee> employees = mapper.findAll(); print(employees); } @Test public void testFindAllWithRole() { IEmployeeDao mapper = sqlSession.getMapper(IEmployeeDao.class); List<Employee> employees = mapper.findAllWithRole(); print(employees); } private void print(List<Employee> objs) { for (Employee obj : objs) { System.out.println(obj); } } @After public void destory() throws IOException { //提交事务,或者放到destory里面 sqlSession.commit(); //6. 释放资源 sqlSession.close(); is.close(); } } 复制代码
testFindAll
Employee(id=1, username=TAA, password=123456, birthday=Fri Dec 13 08:00:00 CST 2019, sex=true, address=北京, roles=null) Employee(id=2, username=TBB, password=123456, birthday=Tue Dec 10 08:00:00 CST 2019, sex=false, address=上海, roles=null) Employee(id=3, username=TCC, password=123456, birthday=Wed Dec 04 08:00:00 CST 2019, sex=true, address=广州, roles=null) 复制代码
testFindAllWithRole
Employee(id=1, username=TAA, password=123456, birthday=Fri Dec 13 08:00:00 CST 2019, sex=true, address=北京, roles=[Role(roleId=2, roleName=校长, roleDesc=管理整个学校), Role(roleId=1, roleName=院长, roleDesc=管理整个学院)]) Employee(id=2, username=TBB, password=123456, birthday=Tue Dec 10 08:00:00 CST 2019, sex=false, address=上海, roles=[Role(roleId=1, roleName=院长, roleDesc=管理整个学院)]) Employee(id=3, username=TCC, password=123456, birthday=Wed Dec 04 08:00:00 CST 2019, sex=true, address=广州, roles=[Role(roleId=3, roleName=班长, roleDesc=管理班级)]) 复制代码
以上就是查询用户下面所有的角色,那么如何查询角色获取角色下所属用户信息呢?
Role实体类加入employees引用:
@Data @AllArgsConstructor @NoArgsConstructor public class Role implements Serializable { private static final long serialVersionUID = -2661131928069328685L; private Integer roleId; private String roleName; private String roleDesc; /** * 一对多关系映射:主表实体类中应该包含从表实体的集合引用 */ private List<Employee> employees; } 复制代码
IRoleDao:
public interface IRoleDao { /** * 查询所有 * * @return 返回数据集合 */ List<Role> findAll(); /** * 查询所有角色并包含用户信息 * * @return 返回数据集合 */ List<Role> findAllWithEmployee(); } 复制代码
IRoleDao.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.ivyzh.dao.IRoleDao"> <!--配置Role的resultMap--> <resultMap id="reMap" type="role"> <id property="roleId" column="rid"/> <result property="roleName" column="role_name"/> <result property="roleDesc" column="role_desc"/> <!--配置role对象中employees集合的映射--> <collection property="employees" ofType="employee"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="password" column="password"/> <result property="sex" column="sex"/> <result property="birthday" column="birthday"/> <result property="address" column="address"/> </collection> </resultMap> <!--查询所有--> <select id="findAll" resultMap="reMap"> select r.id AS rid,r.`role_name`,r.`role_desc` from t_role r </select> <!--查询所有角色并包含用户信息--> <select id="findAllWithEmployee" resultMap="reMap"> SELECT r.id AS rid,r.`role_name`,r.`role_desc`,e.* FROM t_role r LEFT OUTER JOIN t_employee_role er ON r.id = er.`rid` LEFT OUTER JOIN t_employee e ON e.id = er.`cid` </select> </mapper> 复制代码
测试类RoleTest:
/** * RoleTest演示多对多——角色对应的用户信息 */ public class RoleTest { InputStream is; SqlSession sqlSession; @Before public void init() throws IOException { //1. 读取配置文件 is = Resources.getResourceAsStream("SqlMapConfig.xml"); //2. 创建SqlSessionFactory工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(is); //3. 使用工厂生产SqlSession对象 sqlSession = factory.openSession(); } /** * 查询所有用户数据 */ @Test public void testFindAll() { IRoleDao mapper = sqlSession.getMapper(IRoleDao.class); List<Role> roles = mapper.findAll(); print(roles); } @Test public void findAllWithEmployee() { IRoleDao mapper = sqlSession.getMapper(IRoleDao.class); List<Role> roles = mapper.findAllWithEmployee(); print(roles); } private void print(List<Role> objs) { for (Role obj : objs) { System.out.println(obj); } } @After public void destory() throws IOException { //提交事务,或者放到destory里面 sqlSession.commit(); //6. 释放资源 sqlSession.close(); is.close(); } } 复制代码
输出:
testFindAll
Role(roleId=1, roleName=院长, roleDesc=管理整个学院, employees=[]) Role(roleId=2, roleName=校长, roleDesc=管理整个学校, employees=[]) Role(roleId=3, roleName=班长, roleDesc=管理班级, employees=[]) 复制代码
testFindAllWithRole
Role(roleId=2, roleName=校长, roleDesc=管理整个学校, employees=[Employee(id=1, username=TAA, password=123456, birthday=Fri Dec 13 08:00:00 CST 2019, sex=true, address=北京, roles=null)]) Role(roleId=1, roleName=院长, roleDesc=管理整个学院, employees=[Employee(id=1, username=TAA, password=123456, birthday=Fri Dec 13 08:00:00 CST 2019, sex=true, address=北京, roles=null), Employee(id=2, username=TBB, password=123456, birthday=Tue Dec 10 08:00:00 CST 2019, sex=false, address=上海, roles=null)]) Role(roleId=3, roleName=班长, roleDesc=管理班级, employees=[Employee(id=3, username=TCC, password=123456, birthday=Wed Dec 04 08:00:00 CST 2019, sex=true, address=广州, roles=null)]) 复制代码
至此,项目的目录结构如图:
问题:在一对多中,当我们有一个用户,它有100个账户。 在查询用户的时候,要不要把关联的账户查出来? 在查询账户的时候,要不要把关联的用户查出来?
在查询用户时,用户下的账户信息应该是,什么时候使用,什么时候查询的。 在查询账户时,账户的所属用户信息应该是随着账户查询时一起查询出来。
什么是延迟加载 在真正使用数据时才发起查询,不用的时候不查询。按需加载(懒加载) 什么是立即加载 不管用不用,只要一调用方法,马上发起查询。
在对应的四种表关系中:一对多,多对一,一对一,多对多 一对多,多对多:通常情况下我们都是采用延迟加载。 多对一,一对一:通常情况下我们都是采用立即加载。
这个案例可以用Customer和Account这个表来演示,即一个账户对应一个用户。
之前在讲多表查询(一对一)章节的时候略过了,但这里需要用到这个案例,看来还是躲不过去的。
那么我们先用Customer和Account来演示立即加载的情况吧。
注:只演示AccountDao.findById查询账户信息且带出用户信息的情况。
修改Account实体类,加入Customer:
@Data @AllArgsConstructor @NoArgsConstructor public class Account implements Serializable { private static final long serialVersionUID = 1113742752197915016L; private Integer id; private Integer money; private Integer uid; /** * 演示一对一查询 */ private Customer customer; @Override public String toString() { return "Account{" + "id=" + id + ", money=" + money + ", uid=" + uid + "}"; } } 复制代码
IAccountDao:
public interface IAccountDao { /** * 根据id查询账户信息 * * @param id 账户id * @return 账户信息 */ Account findById(Integer id); } 复制代码
IAccountDao.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.ivyzh.dao.IAccountDao"> <!--配置Account的resultMap--> <resultMap id="acMap" type="account"> <id property="id" column="aid"/> <result property="money" column="money"/> <result property="uid" column="uid"/> <!--配置account对象中customer集合的映射--> <collection property="customer" ofType="customer"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="password" column="password"/> <result property="sex" column="sex"/> <result property="birthday" column="birthday"/> <result property="address" column="address"/> </collection> </resultMap> <!--查询所有--> <select id="findById" parameterType="int" resultMap="acMap"> SELECT a.id AS aid,a.money AS money,a.uid AS uid, c.* FROM t_account a,t_customer c WHERE a.id = #{aid} AND a.uid = c.id </select> </mapper> 复制代码
测试类AccountTest:
... /** * 根据id查询账户信息 */ @Test public void testFindById() { IAccountDao mapper = sqlSession.getMapper(IAccountDao.class); Account account = mapper.findById(4); System.out.println(account); System.out.println(account.getCustomer()); } ... 复制代码
输出日志:
SQL:
==> Preparing: SELECT a.id AS aid,a.money AS money,a.uid AS uid, c.* FROM t_account a,t_customer c WHERE a.id = ? AND a.uid = c.id ==> Parameters: 4(Integer) <== Total: 1 复制代码
Account{id=4, money=4000, uid=2} Customer(id=2, username=TBB, password=123456, birthday=Tue Dec 10 08:00:00 CST 2019, sex=false, address=上海, accounts=null) 复制代码
可以发现不仅仅查询到了Account信息,连带着Customer信息也查询出来了。
那么我们改造一下,只有调用的时候才查询。
这里我们要用Assocation实现延迟加载。
IAccountDao里面新加一个方法findById2来演示延迟加载,
public interface IAccountDao { /** * 根据id查询账户信息,立即加载 * * @param id 账户id * @return 账户信息 */ Account findById(Integer id); /** * 根据id查询账户信息,延迟加载 * * @param id 账户id * @return 账户信息 */ Account findById2(Integer id); } 复制代码
改造IAccountDao.xml加入acMapLazy的resultMap和findById2方法:
<?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.ivyzh.dao.IAccountDao"> <!--配置Account的resultMap--> <resultMap id="acMap" type="account"> <id property="id" column="aid"/> <result property="money" column="money"/> <result property="uid" column="uid"/> <!--配置account对象中customer集合的映射--> <collection property="customer" ofType="customer"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="password" column="password"/> <result property="sex" column="sex"/> <result property="birthday" column="birthday"/> <result property="address" column="address"/> </collection> </resultMap> <!--配置Account的resultMap 延迟加载--> <resultMap id="acMapLazy" type="account"> <id property="id" column="aid"/> <result property="money" column="money"/> <result property="uid" column="uid"/> <!--配置account对象中customer集合的映射--> <association property="customer" column="uid" javaType="customer" select="com.ivyzh.dao.ICustomerDao.findById"/> </resultMap> <!--查询所有--> <select id="findById" parameterType="int" resultMap="acMap"> SELECT a.id AS aid,a.money AS money,a.uid AS uid, c.* FROM t_account a,t_customer c WHERE a.id = #{aid} AND a.uid = c.id </select> <select id="findById2" parameterType="int" resultMap="acMapLazy"> select * from t_account where id = #{aid} </select> </mapper> 复制代码
可以发现用到了ICustomerDao的findById方法,这个时候我们也要改造ICustomerDao和ICustomerDao.xml
public interface ICustomerDao { /** * 查询所有 * * @return 返回数据集合 */ List<Customer> findAll(); /** * 查询所有并包含账户信息 * * @return 返回数据集合 */ List<Customer> findAllWithAccount(); /** * 根据id查询用户信息 * * @param id 用户id * @return */ Customer findById(Integer id); } 复制代码
ICustomerDao.xml
... <!--根据id查询用户信息--> <select id="findById" parameterType="int" resultType="customer"> select * from t_customer where id = #{uid} </select> ... 复制代码
AccountTest测试类:
... /** * 根据id查询账户信息-延迟加载 */ @Test public void testFindById2() { IAccountDao mapper = sqlSession.getMapper(IAccountDao.class); Account account = mapper.findById2(1); //System.out.println(account); //System.out.println(account.getCustomer()); } ... 复制代码
为了方便演示,我们将输出注释,只看sql语句就行。
我们运行 testFindById2
方法,观察日志输出:
==> Preparing: select * from t_account where id = ? ==> Parameters: 1(Integer) ====> Preparing: select * from t_customer where id = ? ====> Parameters: 1(Integer) <==== Total: 1 <== Total: 1 复制代码
可以发现执行了两条sql语句,并没有延迟加载啊?原因在哪里呢?
因为Mybatis默认是不开启延迟加载的,如果要开启需要在SqlMapConfig.xml文件里面配置:
... <!--配置参数--> <settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings> ... 复制代码
配置完成之后运行,看日志,发现只有一条SQL语句执行了,说明实现了延迟加载:
==> Preparing: select * from t_account where id = ? ==> Parameters: 1(Integer) <== Total: 1 复制代码
只有主动调用 account.getCustomer()
方法才会执行另一条查询sql。
之前CustomerTest测试类中的testFindAllWithAccount是查询所有信息:
Customer(id=1, username=TAA, password=123456, birthday=Fri Dec 13 08:00:00 CST 2019, sex=true, address=北京, accounts=[Account{id=1, money=1000, uid=1}, Account{id=2, money=2000, uid=1}, Account{id=3, money=3000, uid=1}]) Customer(id=2, username=TBB, password=123456, birthday=Tue Dec 10 08:00:00 CST 2019, sex=false, address=上海, accounts=[Account{id=4, money=4000, uid=2}, Account{id=5, money=5000, uid=2}]) Customer(id=3, username=TCC, password=123456, birthday=Wed Dec 04 08:00:00 CST 2019, sex=true, address=广州, accounts=[Account{id=6, money=6000, uid=3}]) 复制代码
我们改造一下这个方法,期望的效果是当调用查询用户方法的时候,不主动查询与其关联的账户信息。
IAccountDao加入findByUid方法:
public interface IAccountDao { /** * 根据id查询账户信息,立即加载 * * @param id 账户id * @return 账户信息 */ Account findById(Integer id); /** * 根据用户id查询账户信息 * * @param id 用户id * @return 账户信息 */ List<Account> findByUid(Integer id); /** * 根据id查询账户信息,延迟加载 * * @param id 账户id * @return 账户信息 */ Account findById2(Integer id); } 复制代码
IAccountDao.xml实现:
... <select id="findByUid" parameterType="int" resultType="account"> select * from t_account where uid = #{uid} </select> ... 复制代码
ICustomerDao加入findAllWithAccount2方法:
public interface ICustomerDao { /** * 查询所有 * * @return 返回数据集合 */ List<Customer> findAll(); /** * 查询所有并包含账户信息 * * @return 返回数据集合 */ List<Customer> findAllWithAccount(); /** * 查询所有并包含账户信息,延迟加载 * * @return 返回数据集合 */ List<Customer> findAllWithAccount2(); /** * 根据id查询用户信息 * * @param id 用户id * @return */ Customer findById(Integer id); } 复制代码
ICustomerDao.xml实现:
... <!--配置Customer的resultMap--> <resultMap id="caMapLazy" type="customer"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="password" column="password"/> <result property="sex" column="sex"/> <result property="birthday" column="birthday"/> <result property="address" column="address"/> <!--配置customer对象中accounts集合的映射--> <collection property="accounts" column="id" ofType="account" select="com.ivyzh.dao.IAccountDao.findByUid"/> </resultMap> <!--查询所有并包含账户信息-延迟加载--> <select id="findAllWithAccount2" resultMap="caMapLazy"> select * from t_customer </select> ... 复制代码
CustomerTest测试类:
... @Test public void testFindAllWithAccount2() { ICustomerDao mapper = sqlSession.getMapper(ICustomerDao.class); List<Customer> customers = mapper.findAllWithAccount2(); for (Customer customer : customers) { System.out.println("+++" + customer); List<Account> accounts = customer.getAccounts(); for (Account account : accounts) { System.out.println("---" + account); } } } ... 复制代码
输出日志:
==> Preparing: select * from t_customer ==> Parameters: <== Total: 3 ==> Preparing: select * from t_account where uid = ? ==> Parameters: 1(Integer) <== Total: 3 +++Customer{id=1, username='TAA', password='123456', birthday=Fri Dec 13 08:00:00 CST 2019, sex=true, address='北京'} ---Account{id=1, money=1000, uid=1} ---Account{id=2, money=2000, uid=1} ---Account{id=3, money=3000, uid=1} ==> Preparing: select * from t_account where uid = ? ==> Parameters: 2(Integer) <== Total: 2 +++Customer{id=2, username='TBB', password='123456', birthday=Tue Dec 10 08:00:00 CST 2019, sex=false, address='上海'} ---Account{id=4, money=4000, uid=2} ---Account{id=5, money=5000, uid=2} ==> Preparing: select * from t_account where uid = ? ==> Parameters: 3(Integer) <== Total: 1 +++Customer{id=3, username='TCC', password='123456', birthday=Wed Dec 04 08:00:00 CST 2019, sex=true, address='广州'} ---Account{id=6, money=6000, uid=3} 复制代码
把for循环注释了,
@Test public void testFindAllWithAccount2() { ICustomerDao mapper = sqlSession.getMapper(ICustomerDao.class); List<Customer> customers = mapper.findAllWithAccount2(); /* for (Customer customer : customers) { System.out.println("+++" + customer); List<Account> accounts = customer.getAccounts(); for (Account account : accounts) { System.out.println("---" + account); } }*/ } 复制代码
观察输出:
==> Preparing: select * from t_customer ==> Parameters: <== Total: 3 复制代码
至此,完成了一对多的延迟加载
一级缓存:
它指的是Mybatis中SqlSession对象的缓存。
当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中。
该区域的结构是一个Map。当我们再次查询同样的数据,mybatis会先去sqlsession中
查询是否有,有的话直接拿出来用。
当SqlSession对象消失时,mybatis的一级缓存也就消失了。
触发清空一级缓存的情况:
一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。
使用AccountTest这个测试类来演示一级缓存:
/** * AccountFirstCacheTest一级缓存测试 */ public class AccountFirstCacheTest { InputStream is; SqlSession sqlSession; SqlSessionFactory factory; @Before public void init() throws IOException { //1. 读取配置文件 is = Resources.getResourceAsStream("SqlMapConfig.xml"); //2. 创建SqlSessionFactory工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); factory = builder.build(is); //3. 使用工厂生产SqlSession对象 sqlSession = factory.openSession(); } /** * 根据id查询账户信息 */ @Test public void testFindById() { IAccountDao mapper = sqlSession.getMapper(IAccountDao.class); Account account = mapper.findById(4); Account account2 = mapper.findById(4); System.out.println(account); System.out.println(account2); } } 复制代码
运行日志输出:
==> Preparing: SELECT a.id AS aid,a.money AS money,a.uid AS uid, c.* FROM t_account a,t_customer c WHERE a.id = ? AND a.uid = c.id ==> Parameters: 4(Integer) <== Total: 1 Account{id=4, money=4000, uid=2} Account{id=4, money=4000, uid=2} 复制代码
查询不同账户id
... @Test public void testFindById() { IAccountDao mapper = sqlSession.getMapper(IAccountDao.class); Account account = mapper.findById(4); Account account2 = mapper.findById(1); System.out.println(account); System.out.println(account2); } ... 复制代码
日志输出:
==> Preparing: SELECT a.id AS aid,a.money AS money,a.uid AS uid, c.* FROM t_account a,t_customer c WHERE a.id = ? AND a.uid = c.id ==> Parameters: 4(Integer) <== Total: 1 ==> Preparing: SELECT a.id AS aid,a.money AS money,a.uid AS uid, c.* FROM t_account a,t_customer c WHERE a.id = ? AND a.uid = c.id ==> Parameters: 1(Integer) <== Total: 1 Account{id=4, money=4000, uid=2} Account{id=1, money=1000, uid=1} 复制代码
清空sqlSession缓存
... public void testFindById() { IAccountDao mapper = sqlSession.getMapper(IAccountDao.class); Account account = mapper.findById(4); //此方法也可以清空缓存 //sqlSession.clearCache(); sqlSession.close(); sqlSession = factory.openSession(); mapper = sqlSession.getMapper(IAccountDao.class); Account account2 = mapper.findById(4); System.out.println(account); System.out.println(account2); } ... 复制代码
日志:
==> Preparing: SELECT a.id AS aid,a.money AS money,a.uid AS uid, c.* FROM t_account a,t_customer c WHERE a.id = ? AND a.uid = c.id ==> Parameters: 4(Integer) <== Total: 1 Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@78e117e3] Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@78e117e3] Returned connection 2028017635 to pool. Opening JDBC Connection Checked out connection 2028017635 from pool. Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@78e117e3] ==> Preparing: SELECT a.id AS aid,a.money AS money,a.uid AS uid, c.* FROM t_account a,t_customer c WHERE a.id = ? AND a.uid = c.id ==> Parameters: 4(Integer) <== Total: 1 Account{id=4, money=4000, uid=2} Account{id=4, money=4000, uid=2} 复制代码
clearCache
@Test public void testFindById() { IAccountDao mapper = sqlSession.getMapper(IAccountDao.class); Account account = mapper.findById(4); //此方法也可以清空缓存 sqlSession.clearCache(); /*sqlSession.close(); sqlSession = factory.openSession(); mapper = sqlSession.getMapper(IAccountDao.class);*/ Account account2 = mapper.findById(4); System.out.println(account); System.out.println(account2); } 复制代码
日志:
==> Preparing: SELECT a.id AS aid,a.money AS money,a.uid AS uid, c.* FROM t_account a,t_customer c WHERE a.id = ? AND a.uid = c.id ==> Parameters: 4(Integer) <== Total: 1 ==> Preparing: SELECT a.id AS aid,a.money AS money,a.uid AS uid, c.* FROM t_account a,t_customer c WHERE a.id = ? AND a.uid = c.id ==> Parameters: 4(Integer) <== Total: 1 Account{id=4, money=4000, uid=2} Account{id=4, money=4000, uid=2} 复制代码
二级缓存:
它指的是Mybatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。
二级缓存的使用步骤:
第一步:让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)
第二步:让当前的映射文件支持二级缓存(在IAccountDao.xml中配置)
第三步:让当前的操作支持二级缓存(在select标签中配置)
AccountSecondCacheTest:
/** * AccountFirstCacheTest一级缓存测试 */ public class AccountSecondCacheTest { InputStream is; SqlSession sqlSession; SqlSessionFactory factory; @Before public void init() throws IOException { //1. 读取配置文件 is = Resources.getResourceAsStream("SqlMapConfig.xml"); //2. 创建SqlSessionFactory工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); factory = builder.build(is); //3. 使用工厂生产SqlSession对象 sqlSession = factory.openSession(); } /** * 根据id查询账户信息 */ @Test public void testFindById() { IAccountDao mapper = sqlSession.getMapper(IAccountDao.class); Account account = mapper.findById(4); //一级缓存消失,注意这里不能用clearCache方法了 sqlSession.close(); SqlSession sqlSession2 = factory.openSession(); IAccountDao mapper2 = sqlSession2.getMapper(IAccountDao.class); Account account2 = mapper2.findById(4); System.out.println(account); System.out.println(account2); System.out.println(account == account2); } } 复制代码
SqlMapConfig.xml开启二级缓存setting标签:
... <!--配置参数--> <settings> <!--开启二级缓存--> <setting name="cacheEnabled" value="true"/> <!--开启延迟加载功能--> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings> ... 复制代码
IAccountDao.xml映射文件支持二级缓存cache标签:
<mapper namespace="com.ivyzh.dao.IAccountDao"> <!--开启account支持二级缓存--> <cache/> ... </mapper> 复制代码
让当前操作支持二级缓存useCache="true":
... <!--配置Account的resultMap 延迟加载--> <resultMap id="acMapLazy" type="account"> <id property="id" column="aid"/> <result property="money" column="money"/> <result property="uid" column="uid"/> <!--配置account对象中customer集合的映射--> <association property="customer" column="uid" javaType="customer" select="com.ivyzh.dao.ICustomerDao.findById"/> </resultMap> <!--查询所有--> <select id="findById" parameterType="int" resultMap="acMap" useCache="true"> SELECT a.id AS aid,a.money AS money,a.uid AS uid, c.* FROM t_account a,t_customer c WHERE a.id = #{aid} AND a.uid = c.id </select> ... 复制代码
因为在实际开发当中很少使用注解形式,所以具体案例以后再补充。
pagehelper.github.io/
作者有本书《MyBatis 从入门到精通》
PageHelper说白了,是一个拦截器。
pom.xml引入坐标:
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.11</version> </dependency> 复制代码
在 MyBatis 配置 SqlMapConfig.xml 中配置拦截器插件
<!-- plugins在配置文件中的位置必须符合要求,否则会报错,顺序如下: properties?, settings?, typeAliases?, typeHandlers?, objectFactory?,objectWrapperFactory?, plugins?, environments?, databaseIdProvider?, mappers? --> <plugins> <!-- com.github.pagehelper为PageHelper类所在包名 --> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <!-- 使用下面的方式配置参数,后面会有所有的参数介绍 --> <property name="param1" value="value1"/> </plugin> </plugins> 复制代码
分页插件支持以下几种调用方式:
//第一种,RowBounds方式的调用 List<User> list = sqlSession.selectList("x.y.selectIf", null, new RowBounds(0, 10)); //第二种,Mapper接口方式的调用,推荐这种使用方式。 PageHelper.startPage(1, 10); List<User> list = userMapper.selectIf(1); //第三种,Mapper接口方式的调用,推荐这种使用方式。 PageHelper.offsetPage(1, 10); List<User> list = userMapper.selectIf(1); //第四种,参数方法调用 //存在以下 Mapper 接口方法,你不需要在 xml 处理后两个参数 public interface CountryMapper { List<User> selectByPageNumSize( @Param("user") User user, @Param("pageNum") int pageNum, @Param("pageSize") int pageSize); } //配置supportMethodsArguments=true //在代码中直接调用: List<User> list = userMapper.selectByPageNumSize(user, 1, 10); //第五种,参数对象 //如果 pageNum 和 pageSize 存在于 User 对象中,只要参数有值,也会被分页 //有如下 User 对象 public class User { //其他fields //下面两个参数名和 params 配置的名字一致 private Integer pageNum; private Integer pageSize; } //存在以下 Mapper 接口方法,你不需要在 xml 处理后两个参数 public interface CountryMapper { List<User> selectByPageNumSize(User user); } //当 user 中的 pageNum!= null && pageSize!= null 时,会自动分页 List<User> list = userMapper.selectByPageNumSize(user); //第六种,ISelect 接口方式 //jdk6,7用法,创建接口 Page<User> page = PageHelper.startPage(1, 10).doSelectPage(new ISelect() { @Override public void doSelect() { userMapper.selectGroupBy(); } }); //jdk8 lambda用法 Page<User> page = PageHelper.startPage(1, 10).doSelectPage(()-> userMapper.selectGroupBy()); //也可以直接返回PageInfo,注意doSelectPageInfo方法和doSelectPage pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(new ISelect() { @Override public void doSelect() { userMapper.selectGroupBy(); } }); //对应的lambda用法 pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(() -> userMapper.selectGroupBy()); //count查询,返回一个查询语句的count数 long total = PageHelper.count(new ISelect() { @Override public void doSelect() { userMapper.selectLike(user); } }); //lambda total = PageHelper.count(()->userMapper.selectLike(user)); 复制代码
推荐第二种和第三种。
一句话总结:项目初期可以使用,后面就不推荐使用了。
以后再找案例说明。
文章不足之处,还望指正交流。有啥意见和建议的都可以提出来,后续会继续改进:smiley:
准备写一系列的文章MyBatis、MyBatisPlus、Spring、SpringMVC、Oracle、Maven、ElasticSearch、SpringBoot、SpringCloud、RabbitMQ、Nginx...