# MyBatisStudy **Repository Path**: rawchen/MyBatisStudy ## Basic Information - **Project Name**: MyBatisStudy - **Description**: 学习 MyBatis 的记录 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-12-09 - **Last Updated**: 2021-05-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # MyBatisStudy ### MyBatis 3 学习记录4阶段 -------------------------------------------------------- **(1)mybatis入门** 1. mybatis的概述 2. mybatis的环境搭建 3. mybatis入门案例 4. 自定义mybatis框架(主要的目的是为了让大家了解mybatis中执行细节) --------------------------------------------------------------- **(2)mybatis基本使用** 1. mybatis的单表crud操作 2. mybatis的参数和返回值 3. mybatis的dao编写 4. mybatis配置的细节 5. 几个标签的使用 **(3)mybatis的深入和多表** 1. mybatis的连接池 2. mybatis的事务控制及设计的方法 3. mybatis的多表查询 一对多-----多对一------多对多 **(4)mybatis的缓存和注解开发** 1. mybatis中的加载时机(查询的时机) 2. mybatis中的一级缓存和二级缓存 3. mybatis的注解开发 单表CRUD------多表查询 ### 什么是框架? 它是我们软件开发中的一套解决方案,不同的框架解决的是不同的问题。 使用框架的好处:框架封装了很多的细节,使开发者可以使用极简的方式实现功能。大大提高开发效率。 ### 三层架构 表现层:是用于展示数据的 业务层:是处理业务需求 持久层:是和数据库交互的 ### 持久层技术解决方案 1. JDBC技术:**Connection**、**PreparedStatement**、**ResultSet** 2. Spring的**JdbcTemplate**:Spring中对jdbc的简单封装 3. Apache的**DBUtils**:它和Spring的JdbcTemplate很像,也是对Jdbc的简单封装 以上这些都不是框架,JDBC是规范,Spring的JdbcTemplate和Apache的DBUtils都只是工具类 ### mybatis的概述 mybatis是一个持久层框架,用java编写的。 它封装了jdbc操作的很多细节,使开发者只需要关注sql语句本身,而无需关注注册驱动,创建连接等繁杂过程。 它使用了ORM思想实现了结果集的封装。 **ORM:Object Relational Mappging 对象关系映射** 简单的说:就是把数据库表和实体类及实体类的属性对应起来,让我们可以操作实体类就实现操作数据库表。 ## 01_01mybatis入门 **建库建表:mybatistest.sql** ```SQL DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` int(11) NOT NULL auto_increment, `username` varchar(32) NOT NULL COMMENT '用户名称', `birthday` datetime default NULL COMMENT '生日', `sex` char(1) default NULL COMMENT '性别', `address` varchar(256) default NULL COMMENT '地址', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into `user`(`id`,`username`,`birthday`,`sex`,`address`) values (41,'老王','2018-02-27 17:47:08','男','北京'), (42,'小二王','2018-03-02 15:09:37','女','福建'), (43,'小二王','2018-03-04 11:34:34','女','厦门'), (45,'老六','2018-03-04 12:04:06','男','西藏'), (46,'老王','2018-03-07 17:37:26','男','新疆'), (48,'小马宝莉','2018-03-08 11:44:00','女','湖南'); ``` ### mybatis的环境搭建: 1. 创建maven工程并导入坐标 2. 创建实体类和dao的接口 3. 创建Mybatis的主配置文件 SqlMapConifg.xml 4. 创建映射配置文件 UserDao.xml ### 环境搭建的注意事项: 1. 创建UserDao.xml 和 UserDao.java时名称是为了和我们之前的知识保持一致。在Mybatis中它把持久层的操作接口名称和映射文件也叫做:Mapper 所以:UserDao 和 UserMapper是一样的 2. 在idea中创建目录的时候,它和包是不一样的 包在创建时:com.yoyling.dao它是三级结构 目录在创建时:com.yoyling.dao是一级目录 3. mybatis的映射配置文件位置必须和dao接口的包结构相同 4. 映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名 5. 映射配置文件的操作配置(select),id属性的取值必须是dao接口的方法名 当我们遵从了第三,四,五点之后,我们在开发中就无须再写dao的实现类。 ### mybatis的入门案例: 1. 读取配置文件 2. 第二步:创建SqlSessionFactory工厂 3. 创建SqlSession 4. 创建Dao接口的代理对象 5. 执行dao中的方法 6. 释放资源 实体类**User.java** 接口**UserDao**,一个 **List findAll()** **测试main方法:** ```Java //1.读取配置文件 InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.创建SqlSessionFactory工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(in); //3.使用工厂生产SqlSession对象 SqlSession session = factory.openSession(); //4.使用SqlSession创建Dao接口的代理对象 UserDao userDao = session.getMapper(UserDao.class); //5.使用代理对象执行方法 List users = userDao.findAll(); for (User user : users) { System.out.println(user); } //6.释放资源 session.close(); in.close(); ``` **SqlMapConfig.xml** ```xml ``` **UserDao.xml** ```xml ``` ### 注意事项: 不要忘记在映射配置中告知mybatis要封装到哪个实体类中 配置的方式:指定实体类的全限定类名 ## 01_02mybatis_annotation **mybatis基于注解的入门案例:** 把UserDao.xml移除,在dao接口的方法上使用@Select注解,并且指定SQL语句。 同时需要在SqlMapConfig.xml中的mapper配置时,使用class属性指定dao接口的全限定类名。 ```java public interface UserDao { @Select("select * from user") List findAll(); } ``` ```xml ``` **明确:** 我们在实际开发中,都是越简便越好,所以都是采用不写dao实现类的方式。 不管使用XML还是注解配置。 但是Mybatis它是支持写dao实现类的。 ## 01_03mybatis_dao Mybatis的dao实现类 ## 01_04mybatis_design **自定义Mybatis的分析:** mybatis在使用代理dao的方式实现增删改查时做什么事呢? 1. 创建代理对象 2. 在代理对象中调用selectList **自定义mybatis能通过入门案例看到类:** - class Resources - class SqlSessionFactoryBuilder - interface SqlSessionFactory - interface SqlSession ## 02_01mybatisCRUD UserDao.xml ```xml select last_insert_id(); insert into user(username,address,sex,birthday)values(#{userName},#{userAddress},#{userSex},#{userBirthday}); update user set username=#{userName},address=#{userAddress},sex=#{userSex},birthday=#{userBirthday} where id=#{userId}; delete from user where id = #{uid} ``` MybatisTest.java ```java package com.yoyling.test; /** * 测试mybatis的crud操作 */ public class MybatisTest { private InputStream in; private SqlSession sqlSession; private UserDao userDao; @Before //用于在测试方法执行之前执行 public void init() throws Exception { in = Resources.getResourceAsStream("sqlMapConfig.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); sqlSession = factory.openSession(); userDao = sqlSession.getMapper(UserDao.class); } @After //用于在测试方法执行之后执行 public void destroy() throws Exception { //提交事务 sqlSession.commit(); sqlSession.close(); in.close(); } @Test public void testFindAll(){ List users = userDao.findAll(); for (User user : users) { System.out.println(user); } } /** * 测试保存操作 */ @Test public void testSave() { User user = new User(); user.setUserName("yoyling 最后插入"); user.setUserAddress("福建省厦门市"); user.setUserSex("男"); user.setUserBirthday(new Date()); System.out.println("保存操作前:" + user); userDao.saveUser(user); System.out.println("保存操作后:" + user); } /** * 测试更新操作 */ @Test public void testUpdate() { User user = new User(); user.setUserId(50); user.setUserName("mybatis"); user.setUserAddress("福建省漳州市"); user.setUserSex("女"); user.setUserBirthday(new Date()); userDao.updateUser(user); } /** * 测试删除操作 */ @Test public void testDelete() { userDao.deleteUser(50); } /** * 测试查询一个操作 */ @Test public void testFindOne() { User user = userDao.findById(48); System.out.println(user); } /** * 测试模糊查询操作 */ @Test public void testFindByName() { List users = userDao.findByName("%王%"); // List users = userDao.findByName("王"); for (User user : users) { System.out.println(user); } } /** * 测试查询总记录条数操作 */ @Test public void testFindTotal() { int count = userDao.findTotal(); System.out.println(count); } /** * 测试使用QueryVo作为查询条件 */ @Test public void testFindByVo() { QueryVo vo = new QueryVo(); User user = new User(); user.setUserName("%王%"); vo.setUser(user); List users = userDao.findUserByVo(vo); for (User u : users) { System.out.println(u); } } } ``` **resultMap** 配置**查询结果**的列名和实体类的属性名的对应关系 ```xml ``` ```xml ``` ## 03_03one2many 多表关系操作,添加账户 **account** 表: ```SQL DROP TABLE IF EXISTS `account`; CREATE TABLE `account` ( `ID` int(11) NOT NULL COMMENT '编号', `UID` int(11) default NULL COMMENT '用户编号', `MONEY` double default NULL COMMENT '金额', PRIMARY KEY (`ID`), KEY `FK_Reference_8` (`UID`), CONSTRAINT `FK_Reference_8` FOREIGN KEY (`UID`) REFERENCES `user` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into `account`(`ID`,`UID`,`MONEY`) values (1,46,1000),(2,45,1000),(3,46,2000); ``` **MyBatis中的多表查询举例:** * **一对多(多对一): **订单和用户 * **一对一 :** 一人一个身份证号 * **多对多: **老师和学生 以下代码示例:**用户**和**账户** * 一个用户有多个账户 * 一个账户只能属于一个用户 1.建立两表,用户表、账户表 * 之间一对多关系用外键关联 2.建立两实体类,用户类、账户类 * 让实体类体现出一对多关系 3.建立两配置文件 4.实现配置 * 当我们查询用户时,可以同时得到用户下所包含的账户信息 * 当我们查询账户时,可以同时得到账户的所属用户信息 ### 一对一: **第一种关系体现方式:新建AccountUser类**,继承Account,然后加入用户名和地址的属性;在toString()加上super.toString ```Java public class AccountUser extends Account { private String username; private String address; public String getUsername() {return username;} public void setUsername(String username) {this.username = username;} public String getAddress() {return address;} public void setAddress(String address) {this.address = address;} @Override public String toString() { return super.toString()+" AccountUser{" + "username='" + username + '\'' + ", address='" + address + '\'' + '}'; } } ``` **第二种关系体现方式:从表实体包含主表实体的对象引用** Account.java ```Java public class Account implements Serializable { private Integer id; private Integer uid; private double money; //从表实体应该包含一个主表实体的对象引用 private User user; public User getUser() {return user;} public void setUser(User user) {this.user = user;} public Integer getId() {return id;} public void setId(Integer id) {this.id = id;} public Integer getUid() {return uid;} public void setUid(Integer uid) {this.uid = uid;} public double getMoney() {return money;} public void setMoney(double money) {this.money = money;} @Override public String toString() { return "Account{" + "id=" + id + ", uid=" + uid + ", money=" + money + '}'; } } ``` AccountDao.xml ```xml ``` AccountTest.java ```java @Test public void testFindAll(){ List users = accountDao.findAll(); for (Account account : users) { System.out.println(account); System.out.println(account.getUser()); } } ``` ### 一对多: User.java ```Java public class User implements Serializable { private Integer id; private String username; private String sex; private String address; private Date birthday; //一对多关系映射:主表实体应该包含从表实体的集合引用 private List accounts; public List getAccounts() { return accounts; } public void setAccounts(List accounts) { this.accounts = accounts; } ...... } ``` UserDao.xml ```xml ``` UserTest.java ```Java @Test public void testFindAll(){ List users = userDao.findAll(); for (User user : users) { System.out.println(user); System.out.println(user.getAccounts()); } } ``` ## 03_04many2many ### 多对多: 示例:用户和角色 一个用户多个角色,一个角色可赋予多个用户 使用中间表,包含各自主键。在中间表为外键。两实体类各自包含对方一个集合引用。 数据库新建两个表 **role** 和 **user_role**,为角色表,和用户角色中间表。 ```sql DROP TABLE IF EXISTS `role`; CREATE TABLE `role` ( `ID` int(11) NOT NULL COMMENT '编号', `ROLE_NAME` varchar(30) default NULL COMMENT '角色名称', `ROLE_DESC` varchar(60) default NULL COMMENT '角色描述', PRIMARY KEY (`ID`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into `role`(`ID`,`ROLE_NAME`,`ROLE_DESC`) values (1,'院长','管理整个学院'),(2,'总裁','管理整个公司'),(3,'校长','管理整个学校'); DROP TABLE IF EXISTS `user_role`; CREATE TABLE `user_role` ( `UID` int(11) NOT NULL COMMENT '用户编号', `RID` int(11) NOT NULL COMMENT '角色编号', PRIMARY KEY (`UID`,`RID`), KEY `FK_Reference_10` (`RID`), CONSTRAINT `FK_Reference_10` FOREIGN KEY (`RID`) REFERENCES `role` (`ID`), CONSTRAINT `FK_Reference_9` FOREIGN KEY (`UID`) REFERENCES `user` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into `user_role`(`UID`,`RID`) values (41,1),(45,1),(41,2); ``` Role.java ```java private Integer roleId; private String roleName; private String roleDesc; //多对多的关系映射:一个角色可以赋予给多个用户 private List users; public List getUsers() {return users;} public void setUsers(List users) {this.users = users;} ``` RoleDao.xml ```xml ``` 得到输出结果: ```shell Role{roleId=41, roleName='院长', roleDesc='管理整个学院'} [User{id=41, username='老王', sex='男', address='北京', birthday=Tue Feb 27 17:47:08 CST 2018}] Role{roleId=45, roleName='院长', roleDesc='管理整个学院'} [User{id=45, username='老六', sex='男', address='西藏', birthday=Sun Mar 04 12:04:06 CST 2018}] Role{roleId=null, roleName='校长', roleDesc='管理整个学校'} ``` ------ User.java ```java private Integer id; private String username; private String sex; private String address; private Date birthday; //多对多的关系映射:一个用户可以具备多个角色 private List roles; public List getRoles() {return roles;} public void setRoles(List roles) {this.roles = roles;} ``` UserDao.xml ```xml ``` 测试得到输出: ```Java @Test public void testFindAll(){ List users = userDao.findAll(); for (User user : users) { System.out.println(user); System.out.println(user.getRoles()); } } ``` ```shell User{id=41, username='老王', sex='男', address='北京', birthday=Tue Feb 27 17:47:08 CST 2018} [Role{roleId=1, roleName='院长', roleDesc='管理整个学院'}, Role{roleId=2, roleName='总裁', roleDesc='管理整个公司'}] User{id=42, username='小二王', sex='女', address='福建', birthday=Fri Mar 02 15:09:37 CST 2018} [] User{id=43, username='小二王', sex='女', address='厦门', birthday=Sun Mar 04 11:34:34 CST 2018} [] User{id=45, username='老六', sex='男', address='西藏', birthday=Sun Mar 04 12:04:06 CST 2018} [Role{roleId=1, roleName='院长', roleDesc='管理整个学院'}] User{id=46, username='老王', sex='女', address='新疆', birthday=Wed Mar 07 17:37:26 CST 2018} [] User{id=48, username='小马宝莉', sex='女', address='湖南', birthday=Thu Mar 08 11:44:00 CST 2018} [] ``` ## 03_05jndi SqlMapConfig.xml ```xml ``` META-INF -> context.xml ```xml ``` index.jsp ```java <% //1.读取配置文件 InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.创建SqlSessionFactory工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(in); //3.使用工厂生产SqlSession对象 SqlSession sqlSession = factory.openSession(); //4.使用SqlSession创建Dao接口的代理对象 UserDao userDao = sqlSession.getMapper(UserDao.class); //5.使用代理对象执行方法 List users = userDao.findAll(); for (User user : users) { System.out.println(user); } //6.释放资源 sqlSession.close(); in.close(); %> ``` ## 04_01lazy **延迟加载:** 在真正使用数据时才发起查询,不用时不查询。按需加载(懒加载)。 **立即加载:** 不管用不用只要一调用方法,马上发起查询。 **一对多,多对多:通常用延迟加载。** **多对一,一对一:通常用立即加载。** 用的时候调用对方配置文件中的一个配置来实现延迟查询功能。 AccountDao.xml ```xml ``` SqlMapConfig.xml ```xml ``` ## 04_02cache **缓存:**减少和数据库的交互次数,提高执行效率。 经常查询并且不经常改变、数据的正确与否对最终结果影响不大的数据。 **一级缓存:** SqlSession对象的缓存。当我们执行查询时,查询结果会同时存入到SqlSession提供的一区域,结构为map。 当调用SqlSession的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。 ```java @Test public void testFirstLevelCache(){ User user1 = userDao.findById(41); System.out.println(user1); User user2 = userDao.findById(41); System.out.println(user2); System.out.println(user1==user2); } ``` ```java @Test public void testFirstLevelCache(){ User user1 = userDao.findById(41); System.out.println(user1); //再次获取SqlSession对象 //sqlSession.close(); //sqlSession = factory.openSession(); sqlSession.clearCache(); userDao = sqlSession.getMapper(UserDao.class); User user2 = userDao.findById(41); System.out.println(user2); System.out.println(user1==user2); } ``` **二级缓存:** Mybatis中SqlSessionFactory对象的缓存,由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。 二级缓存存放的内容是数据不是对象,因此前后不是同一个对象。 **步骤:** 1. 让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置) 2. 让当前的映射文件支持二级缓存(在UserDao.xml中配置) 3. 让当前的操作支持二级缓存(在select标签中配置) SqlMapConfig.xml ```xml ``` UserDao.xml ```xml ``` ## 04_03annotation_mybatis UserDao.java ```java package com.yoyling.dao; /** * CRUD四个注解: @Select @Insert @Update @Delete */ public interface UserDao { /** * 查询所有用户 * @return */ @Select("select * from user") List findAll(); /** * 保存用户 * @param user */ @Insert("insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday})") void saveUser(User user); /** * 更新用户 * @param user */ @Update("update user set username = #{username},address = #{address},sex = #{sex},birthday = #{birthday} where id = #{id}") void updateUser(User user); /** * 删除用户 * @param userId */ @Delete("delete from user where id = #{id}") void deleteUser(Integer userId); /** * 根据id查询用户 * @param userId * @return */ @Select("select * from user where id = #{id}") User findById(Integer userId); /** * 根据用户名称模糊查询 * @param userName * @return */ //@Select("select * from user where username like #{username}") @Select("select * from user where username like '%${value}%'") List findUserByName(String userName); /** * 查询总用户数 * @return */ @Select("select count(*) from user") int findTotalUser(); } ``` ## 04_04annoOne2Many **实体属性和数据库表列名的对应:** UserDao.java ```java /** * 查询所有用户 * @return */ @Select("select * from user") @Results(id="userMap",value={ @Result(id = true,column = "id",property = "userId"), @Result(column = "username",property = "userName"), @Result(column = "address",property = "userAddress"), @Result(column = "sex",property = "userSex"), @Result(column = "birthday",property = "userBirthday") }) List findAll(); /** * 根据id查询用户 * @param userId * @return */ @Select("select * from user where id = #{id}") @ResultMap("userMap") User findById(Integer userId); ``` Account.java ```java private Integer id; private Integer uid; private Double money; //多对一(Mybatis中称为一对一)的映射:一个账户只能属于一个用户 private User user; public User getUser() {return user;} public void setUser(User user) {this.user = user;} ``` AccoundDao.java ```java /** * 查询所有账户,并且获取每个账户所属的用户信息 * @return */ @Select("select * from account") @Results(id = "accountMap",value = { @Result(id = true,column = "id",property = "id"), @Result(column = "uid",property = "uid"), @Result(column = "money",property = "money"), @Result(property = "user",column = "uid",one = @One(select = "com.yoyling.dao.UserDao.findById",fetchType = FetchType.EAGER)) }) List findAll(); ``` **一对多:** User.java ```java //一对多关系映射:一个用户对应多个账户 private List accounts; ``` AccountDao.java ```java /** * 根据用户id查询账户信息 * @param userId * @return */ @Select("select * from account where uid = #{userId}") List findAccountByUid(Integer userId); ``` UserDao.java ```java /** * 查询所有用户 * @return */ @Select("select * from user") @Results(id="userMap",value={ @Result(id = true,column = "id",property = "userId"), @Result(column = "username",property = "userName"), @Result(column = "address",property = "userAddress"), @Result(column = "sex",property = "userSex"), @Result(column = "birthday",property = "userBirthday"), @Result(property = "accounts",column = "id",many = @Many(select = "com.yoyling.dao.AccountDao.findAccountByUid",fetchType = FetchType.LAZY)) }) List findAll(); ``` **注解开启二级缓存:** UserDao.java ```java @CacheNamespace(blocking = true) public interface UserDao {...} ``` SqlMapConfig.xml ```xml ``` SecondLevelCacheTest.java ```java @Test public void testFindOne() { SqlSession session = factory.openSession(); UserDao userDao = session.getMapper(UserDao.class); User user = userDao.findById(41); System.out.println(user); session.close();//释放一级缓存 SqlSession session1 = factory.openSession();//再次打开session UserDao userDao1 = session1.getMapper(UserDao.class); User user1 = userDao1.findById(41); System.out.println(user1); session1.close(); } ```