在出差隔离的这段时间,成功把MyBatis系统性的学习了一遍。期间穿插着一些商城项目的开发。

MyBatis

环境:

  • JDK1.8

  • MySQL 5.7

  • Maven 3.6.1

  • IDEA

回顾:

  • JDBC

  • MySQL

  • Java基础(继承、封装)

  • Maven

  • Junit

SSM框架学习:配置文件。

最好的方式:查看官网文档https://mybatis.org/mybatis-3/zh/index.html中文文档

1、简介

1.1、什么是MyBatis?

MyBatis 是一款优秀的持久层框架

它支持自定义 SQL、存储过程以及高级映射。

MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。

2013年11月迁移到Github

如何获得MyBatis?

1.2、持久化

数据持久化:将程序的数据在持久状态和瞬时状态转化的过程(数据库存取)

内存:断电即失

数据库(JDBC),io文件持久化

生活:冷藏、罐头……

为什么需要持久化?

有一些对象数据不能丢失,需要存储,需要的时候再读取。

内存贵

1.3、持久层

Dao层、Service层、Controller层……

  • 完成持久化工作的代码块

  • 层界限十分明显

1.4、为什么需要MyBatis?

  • 帮助将数据存入到数据库中

  • 方便

  • 传统的JDBC代码太复杂了,简化,框架,自动化

  • 不用也可以。但此技术容易上手。技术没有高低之分

特点:

  • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。

  • 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。

  • 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。

  • 提供映射标签,支持对象与数据库的orm字段关系映射

  • 提供对象关系映射标签,支持对象关系组建维护

  • 提供xml标签,支持编写动态sql。

最重要的一点:使用的人多

Spring SpringMVC SpringBoot

2、第一个MyBatis程序

思路:搭建环境->导入MyBatis->编写代码->测试

2.1、搭建环境

搭建数据库

 
CREATE DATABASE `mybatis`;
 ​
 USE `mybatis`;
 ​
 CREATE TABLE `user`(
     `id` INT(20) NOT NULL PRIMARY KEY,
     `name` VARCHAR (30) DEFAULT NULL,
     `pwd` VARCHAR(30) DEFAULT NULL
 )ENGINE=INNODB DEFAULT CHARSET=utf8;
 ​
 INSERT INTO `user`(`id`,`name`,`pwd`)VALUES
 (1,'zhou','123456'),
 (2,'rong','234567'),
 (3,'ming','345678')

 

新建项目

  1. 新建一个普通的Maven项目(无模板)

  2. 修改Maven设置,选择正确的版本文件位置,删除src文件夹(便于后续增加模块时将该模块视为父模块)

  3. 导入Maven依赖

 <!--    导入依赖-->
     <dependencies>
 <!--        mysql驱动-->
         <dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-java</artifactId>
             <version>5.1.47</version>
         </dependency>
 <!--        mybatis-->
         <dependency>
             <groupId>org.mybatis</groupId>
             <artifactId>mybatis</artifactId>
             <version>3.5.3</version>
         </dependency>
 <!--        junit-->
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <version>4.12</version>
         </dependency>
     </dependencies>

 

2.2、创建一个模块

  • 编写mybatis的核心配置文件mybatis-config.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>
     <environments default="development">
         <environment id="development">
             <transactionManager type="JDBC"/>
             <dataSource type="POOLED">
                 <property name="driver" value="com.mysql.jdbc.Driver"/>
                 <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=GMT%2B8"/>
                 <property name="username" value="${username}"/>
                 <property name="password" value="${password}"/>
             </dataSource>
         </environment>
     </environments>
 </configuration>

 

  • 编写mybatis工具类

 package com.zhou.utils;
 ​
 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 java.io.IOException;
 import java.io.InputStream;
 ​
 //sqlSessionFactory --> sqlSession
 public class MyBatisUtils {
 ​
     private static SqlSessionFactory sqlSessionFactory;
 ​
     static {
         try {
             //使用mybatis第一步:获取SqlSessionFactory对象
             String resource = "mybatis-config.xml";
             InputStream inputStream = Resources.getResourceAsStream(resource);
             SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
         } catch (IOException e) {
             e.printStackTrace();
         }
     }
 ​
 //    既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
 //    SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
public static SqlSession getSqlSession(){
         return sqlSessionFactory.openSession();
     }
 }

 

2.3、编写代码

  • 实体类

 package com.zhou.pojo;
 ​
 public class User {
     private int id;
     private String name;
     private String pwd;
 ​
     public User() {
     }
 ​
     public User(int id, String name, String pwd) {
         this.id = id;
         this.name = name;
         this.pwd = pwd;
     }
 ​
     public int getId() {
         return id;
     }
 ​
     public void setId(int id) {
         this.id = id;
     }
 ​
     public String getName() {
         return name;
     }
 ​
     public void setName(String name) {
         this.name = name;
     }
 ​
     public String getPwd() {
         return pwd;
     }
 ​
     public void setPwd(String pwd) {
         this.pwd = pwd;
     }
 ​
     @Override
     public String toString() {
         return "User{" +
                 "id=" + id +
                 ", name='" + name + '\'' +
                 ", pwd='" + pwd + '\'' +
                 '}';
     }
 }

 

  • Dao接口

 package com.zhou.dao;
 ​
 import com.zhou.pojo.User;
 ​
 import java.util.List;
 ​
 public interface UserDao {
     List<User> getUserList();
 }

 

  • 接口实现类由JDBC的UserDaoImpl转换为一个Mapper配置文件UserMapper.xml

注意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">
 <!--namespace绑定一个对应的Dao/Mapper接口-->
 <mapper namespace="com.zhou.dao.UserDao">
 <!--    查询语句,id为方法名,resultType为返回类型,需要精确目录-->
     <select id="getUserList" resultType="com.zhou.pojo.User">
         select * from mybatis.user
     </select>
 </mapper>

 

2.4、测试

junit测试

在maven的test文件夹里执行,且目录与src对应,且在测试类名后加Test(命名和目录规范)

 package com.zhou.dao;
 ​
 import com.zhou.pojo.User;
 import com.zhou.utils.MyBatisUtils;
 import org.apache.ibatis.session.SqlSession;
 import org.junit.Test;
 ​
 import java.util.List;
 ​
 public class UserDaoTest {
     @Test
     public void test(){
 ​
         //1.获取SQLSession对象
         SqlSession sqlSession = MyBatisUtils.getSqlSession();
 ​
         //2.执行SQL
         //方式一:getMapper(推荐)使用正确描述每个语句的参数和返回值的接口,不需要强转类型,比较安全
         UserDao userDao = sqlSession.getMapper(UserDao.class);
         List<User> userList = userDao.getUserList();
 ​
         //方式二:selectList
         List<User> userList1 = sqlSession.selectList("com.zhou.dao.UserDao.getUserList");
 ​
 ​
         for (User user : userList) {
             System.out.println(user);
         }
         System.out.println("=================");
         for (User user : userList1) {
             System.out.println(user);
         }
 ​
         //3.关闭Session
         sqlSession.close();
     }
 }

 


 

可能会遇到的问题:

  1. 配置文件没有注册

org.apache.ibatis.binding.BindingException: Type interface com.zhou.dao.UserDao is not known to the MapperRegistry.

需要去核心配置文件mybatis-config注册mapper

 <mappers>
  <mapper resource="com/zhou/dao/UserMapper.xml"/>
 </mappers>
  1. 绑定接口错误namespace

 
<?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.zhou.dao.UserDao">
  <select id="getUserList" resultType="com.zhou.pojo.User">
      select * from mybatis.user;
  </select>
 </mapper>

 

UserMapper.xml

  1. 方法名

  2. 返回类型不对

  3. Maven导出资源问题

java.lang.ExceptionInInitializerError

Caused by: java.io.IOException: Could not find resource com/zhou/dao/UserMapper.xml

资源过滤问题,该xml文件不在target文件夹内。写的配置文件无法导出或生效

需要手动在build中配置resources过滤filter

在pom文件中加上:

 
<build>
  <resources>
    <resource>
      <directory>
        src/main/resources
      </directory>
      <excludes>
        <exclude>**/*.properties</exclude>
        <exclude>**/*.xml</exclude>
      </excludes>
      <filtering>true</filtering>
    </resource>
    <resource>
      <directory>
        src/main/java
      </directory>
      <excludes>
        <exclude>**/*.properties</exclude>
        <exclude>**/*.xml</exclude>
      </excludes>
      <filtering>true</filtering>
    </resource>
  </resources>
 </build>

 

如果继续出现

java.io.IOException: Could not find resource mybatis-config.xml

需要将路径进行变换,resource资源文件目录范围需要包括mybatis-config.xml对应所在的目录

对build进行修改:

 
<build>
  <resources>
      <resource>
          <directory>
              src/main/resources
          </directory>
          <includes>
              <include>**/*.properties</include>
              <include>**/*.xml</include>
          </includes>
      </resource>
      <resource>
          <directory>
              src/main/java
          </directory>
          <includes>
              <include>**/*.properties</include>
              <include>**/*.xml</include>
          </includes>
      </resource>
  </resources>
 </build>
  1. 不要忘记打开SQL服务,确认连接上了数据库

成功结果:

User{id=1, name='zhou', pwd='123456'} User{id=2, name='rong', pwd='234567'} User{id=3, name='ming', pwd='345678'}

======

User{id=1, name='zhou', pwd='123456'} User{id=2, name='rong', pwd='234567'} User{id=3, name='ming', pwd='345678'}

3、CRUD增删改查

3.1、namespace

namespace中的包名要和Dao/Mapper接口的包名一致

3.2、Select 查询

3.3、Insert 插入

3.4、Update 更改

3.5、Delete 删除

3.6、使用

  1. 编写接口interface UserMapper

 
package com.zhou.dao;
 ​
 import com.zhou.pojo.User;
 ​
 import java.util.List;
 ​
 public interface UserMapper {
     //查询全部用户
     List<User> getUserList();
 ​
     //根据ID查询用户
     User getUserById(int id);
 ​
     //插入一个用户
     int addUser(User user);
 ​
     //修改一个用户
     int updateUser(User user);
 ​
     //删除一个用户
     int deleteUser(int id);
 }

 

  1. 编写Mapper中对应的SQL语句

UserMapper.xml中

  • id:就是对应的namespace中的方法名

  • resultType:sql语句执行的返回值

  • parameterType:传递的参数类型

 
<?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.zhou.dao.UserMapper">
     <insert id="addUser" parameterType="com.zhou.pojo.User">
         insert into mybatis.user (id,name,pwd) values (#{id},#{name},#{pwd});
     </insert><update id="updateUser" parameterType="com.zhou.pojo.User">
         update mybatis.user set name=#{name},pwd=#{pwd} where id = #{id};
     </update><delete id="deleteUser" parameterType="int">
         delete from mybatis.user where id = #{id};
     </delete><select id="getUserList" resultType="com.zhou.pojo.User">
         select * from mybatis.user;
     </select><select id="getUserById" resultType="com.zhou.pojo.User" parameterType="int">
         select * from mybatis.user where id = #{id};
     </select>
 </mapper>

 

  1. 测试UserDaoTest

注意增删改需要提交事务,数据库才能更新

 package com.zhou.dao;
 ​
 import com.zhou.pojo.User;
 import com.zhou.utils.MyBatisUtils;
 import org.apache.ibatis.session.SqlSession;
 import org.junit.Test;
 ​
 import java.util.List;
 ​
 public class UserDaoTest {
     @Test
     public void test(){
 ​
         //1.获取SQLSession对象
         SqlSession sqlSession = MyBatisUtils.getSqlSession();
 ​
         //2.执行SQL
         //方式一:getMapper(推荐)使用正确描述每个语句的参数和返回值的接口,不需要强转类型,比较安全
         UserMapper userDao = sqlSession.getMapper(UserMapper.class);
         List<User> userList = userDao.getUserList();
 ​
         //方式二:selectList
         List<User> userList1 = sqlSession.selectList("com.zhou.dao.UserMapper.getUserList");
 ​
 ​
         for (User user : userList) {
             System.out.println(user);
         }
         System.out.println("=================");
         for (User user : userList1) {
             System.out.println(user);
         }
 ​
         //3.关闭Session
         sqlSession.close();
     }
 ​
     @Test
     public void getUserByIdTest(){
         //固定格式一:建立SQLSession
         SqlSession sqlSession = MyBatisUtils.getSqlSession();
         //固定格式二:创立mapper映射
         UserMapper mapper = sqlSession.getMapper(UserMapper.class);
 ​
         //使用mapper对应的UserMapper接口的所有方法
         User user = mapper.getUserById(1);
         System.out.println(user);
 ​
         //固定格式三:关闭SQLSession连接
         sqlSession.close();
     }
 ​
     //增删改需要增加事务
     @Test
     public void addUser(){
         SqlSession sqlSession = MyBatisUtils.getSqlSession();
         UserMapper mapper = sqlSession.getMapper(UserMapper.class);
 ​
         int result = mapper.addUser(new User(4, "zhang", "233333"));
         if (result > 0){
             System.out.println("插入成功!");
         }
 ​
         //提交事务
         sqlSession.commit();
 ​
         sqlSession.close();
     }
 ​
     @Test
     public void updateUser(){
         SqlSession sqlSession = MyBatisUtils.getSqlSession();
         UserMapper mapper = sqlSession.getMapper(UserMapper.class);
         int result = mapper.updateUser(new User(4, "li", "123123"));
         if (result > 0){
             System.out.println("更改成功!");
         }
 ​
         sqlSession.commit();
         sqlSession.close();
     }
 ​
     @Test
     public void deleteUser(){
         SqlSession sqlSession = MyBatisUtils.getSqlSession();
         UserMapper mapper = sqlSession.getMapper(UserMapper.class);
         int result = mapper.deleteUser(4);
         if (result > 0){
             System.out.println("删除成功!");
         }
 ​
         sqlSession.commit();
         sqlSession.close();
     }
 }

 

读错误从后往前读

3.7、万能Map

假设实体类或者数据库中的表,字段或者参数过多,应当考虑使用Map

可以随时添加键值对(可定制化)

Map传递参数,直接在sql中取出key即可 parameterType="map"

对象传递 ,直接在sql中取对象的属性即可 parameterType="Object"

只有一个基本类型参数的情况下,可以直接在sql中渠道

多个参数用Map或者注解

增加接口

 int addUser2(Map<String,Object> map);
 User getUserById2(Map<String,Object> map);

 

增加SQL映射

 <insert id="addUser2" parameterType="map">
     insert into mybatis.user (id,name,pwd) values (#{userid},#{userName},#{password});
 </insert>
 <select id="getUserById2" parameterType="map" resultType="com.zhou.pojo.User">
     select * from mybatis.user where id = #{userid} and name = #{username};
 </select>

 

填入数值

 @Test
 public void addUser2(){
     SqlSession sqlSession = MyBatisUtils.getSqlSession();
     UserMapper mapper = sqlSession.getMapper(UserMapper.class);
     HashMap<String, Object> map = new HashMap<String, Object>();
     map.put("userid",5);
     map.put("userName","Hello");
     map.put("password","22223333");
 ​
     mapper.addUser2(map);
     sqlSession.close();
 }
 @Test
 public void getUserByIdTest2(){
     SqlSession sqlSession = MyBatisUtils.getSqlSession();
     UserMapper mapper = sqlSession.getMapper(UserMapper.class);
 ​
     Map<String,Object> map = new HashMap<String,Object>();
 ​
     map.put("userid",1);
     map.put("username","zhou");
 ​
     mapper.getUserById2(map);
 ​
     sqlSession.close();
 }

 

3.8、模糊查询

防止sql注入,用户输入的值保证安全

like

Java代码执行的时候,传递通配符%?%

 List<User> userList = mapper.getUserLike("%ing%");

或在sql拼接中使用通配符

 <select id="getUserLike" parameterType="string" resultType="com.zhou.pojo.User">
     select * from mybatis.user where name like "%"#{value}"%"
 </select>

 

 @Test
 public void getUserLike(){
     SqlSession sqlSession = MyBatisUtils.getSqlSession();
     UserMapper mapper = sqlSession.getMapper(UserMapper.class);
     List<User> userList = mapper.getUserLike("ing");
     for (User user : userList) {
         System.out.println(user);
     }
     sqlSession.close();
 }

 

4、配置解析

4.1、核心配置文件

mybatis-config.xml

configuration(配置) properties(属性) settings(设置) typeAliases(类型别名) typeHandlers(类型处理器) objectFactory(对象工厂) plugins(插件) environments(环境配置) environment(环境变量) transactionManager(事务管理器) dataSource(数据源) databaseIdProvider(数据库厂商标识) mappers(映射器)

4.2、环境配置(environments)

MyBatis 可以配置成适应多种环境

可以配置多个environment,在default中选择

尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

Mybatis默认的事务管理器是JDBC,连接池:POOLED

4.3、属性(properties)

通过properties属性实现引用配置文件

这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。【db.properties】

编写一个配置文件db.properties

 driver=com.mysql.jdbc.Driver
 url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8
 username=root
 password=root

在核心配置中引入

 <!--    引入外部配置文件-->
     <properties resource="db.properties">
         <property name="username" value="root"/>
         <property name="password" value="root"/>
     </properties>

 

可以直接引入外部文件resource

也可以在其中增加一些属性配置property

如果两个文件有同一字段,优先使用外部配置文件

4.4、类型别名(typeAliases)

类型别名可为 Java 类型设置一个缩写名字。

它仅用于 XML 配置,意在降低冗余的全限定类名书写

 <typeAliases>
     <typeAlias type="com.zhou.pojo.User" alias="User"></typeAlias>
 </typeAliases>

 

也可以指定一个包名,Mybatis会在包名下面搜索需要的Java Bean,比如:

扫描实体类的包,它的默认别名就为这个类的 类名,首字母小写

 <typeAliases>
     <package name="com.zhou.pojo"/>
 </typeAliases>

 

在实体类比较少的时候,使用第一种方式

如果实体类十分多,建议使用第二种

第一种可以DIY别名,第二名需要通过在实体类上增加alias注解

 @Alias("User")
 public class User {
    ...
 }

4.5、设置(settings)

设置名描述有效值默认值
logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING 未设置
cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 true | false true
lazyLoadingEnabled(懒加载) 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 true | false false

4.6、其他配置

typeAliases(类型别名) typeHandlers(类型处理器) objectFactory(对象工厂)

plugins插件

  • mybatis-generator-core

  • mybatis-plus

  • 通用mapper

4.7、映射器(mappers)

MapperRegistry:注册绑定Mapper文件

方式一:xml文件扫描(推荐)

 <mappers>
     <mapper resource="com/zhou/dao/UserMapper.xml"/>
 </mappers>

 

方式二:类扫描

 <!--    每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
 <mappers>
     <mapper class="com.zhou.dao.UserMapper"/>
 </mappers>

 

方式三:包扫描

 <mappers>
     <package name="com.zhou.dao"/>
 </mappers>

 

注意点:方式二、三

UserMapper接口和它的Mapper配置文件必须同名

UserMapper接口和Mapper配置文件必须在同一包下

4.8、生命周期和作用域

错误的使用会导致严重的并发问题

SqlSessionFactoryBuilder

创建SqlSessionFactory

这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。

作为局部变量使用

SqlSessionFactory

可以想象为数据库连接池

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。

因此 SqlSessionFactory 的最佳作用域是应用作用域作为全局变量使用

最简单的就是使用单例模式或者静态单例模式。

SqlSession

可以想象为连接到连接池的一个请求

使用完毕后需要关闭请求,否则资源被占用。每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它

SqlSession 的实例不是线程安全的,因此是不能被共享的

所以它的最佳的作用域是请求或方法作用域

每一个mapper都对应一个具体业务

5、解决属性名和字段名不一致的问题

5.1、问题引入

pwd->password

数据库中的字段:

user:

id

name

pwd

新建一个项目,拷贝之前的内容,测试实体类字段不一致的情况

 public class User {
     private int id;
     private String name;
     private String password;
 }

 

开启测试

 package com.zhou.dao;
 ​
 import com.zhou.pojo.User;
 import com.zhou.utils.MyBatisUtils;
 import org.apache.ibatis.session.SqlSession;
 import org.junit.Test;
 ​
 import java.util.List;
 ​
 public class UserDaoTest {
 ​
     @Test
     public void getUserLike(){
         SqlSession sqlSession = MyBatisUtils.getSqlSession();
         UserMapper mapper = sqlSession.getMapper(UserMapper.class);
         User user = mapper.getUserById(1);
         System.out.println(user);
 ​
         sqlSession.close();
     }
 }

 

得到测试结果,发现问题

User{id=1, name='zhou', password='null'}

分析:

 select * from mybatis.user where id = #{id};
 -- 通过类型处理器
 select id,name,pwd from mybatis.user where id = #{id};
 -- 找不到对应的password,需要告知pwd才能查到

 

解决方法:

采用别名

 select id,name,pwd as password from mybatis.user where id = #{id};

 

5.2、ResultMap

结果集映射

id name pwd

id name password

 <!--    结果集映射-->
     <resultMap id="UserMap" type="com.zhou.pojo.User">
 <!--        column数据库中的字段,property实体类中的属性-->
         <result column="id" property="id"/>
         <result column="name" property="name"/>
         <result column="pwd" property="password"/>
     </resultMap><select id="getUserById" resultMap="UserMap" >
         select * from mybatis.user where id = #{id};
     </select>

 

对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

只需要添加解决列名不匹配的部分,其他匹配的可以不用配置,并不需要所有都匹配

6、日志

6.1、日志工厂

如果一个数据库操作出现了异常,需要排错。日志是最好助手

使用日志工厂取代sout、debug

配置参数,在mybatis核心配置文件中设定

设置名描述有效值默认值
logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING 未设置

STDOUT_LOGGING 标准日志输出

 <settings>
     <setting name="logImpl" value="STDOUT_LOGGING"/>
 </settings>

 

日志输出结果示意:

 Opening JDBC Connection
 Created connection 1859039536.
 Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6eceb130]
 ==> Preparing: select * from mybatis.user where id = ?;
 ==> Parameters: 1(Integer)
 <==   Columns: id, name, pwd
 <==       Row: 1, zhou, 123456
 <==     Total: 1
 User{id=1, name='zhou', password='123456'}
 Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6eceb130]
 Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@6eceb130]
 Returned connection 1859039536 to pool.

6.2、LOG4J

Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件

也可以控制每一条日志的输出格式;

通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。

这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

1.导包

 <dependency>
     <groupId>log4j</groupId>
     <artifactId>log4j</artifactId>
     <version>1.2.17</version>
 </dependency>

 

2.填写log4j.properties

 #将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
 log4j.rootLogger=DEBUG,console,file
 
 #控制台输出的相关设置
 log4j.appender.console = org.apache.log4j.ConsoleAppender
 log4j.appender.console.Target = System.out
 log4j.appender.console.Threshold=DEBUG
 log4j.appender.console.layout = org.apache.log4j.PatternLayout
 log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
 
 #文件输出的相关设置
 log4j.appender.file = org.apache.log4j.RollingFileAppender
 log4j.appender.file.File=./log/zhou.log
 log4j.appender.file.MaxFileSize=10mb
 log4j.appender.file.Threshold=DEBUG
 log4j.appender.file.layout=org.apache.log4j.PatternLayout
 log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
 
 #日志输出级别
 log4j.logger.org.mybatis=DEBUG
 log4j.logger.java.sql=DEBUG
 log4j.logger.java.sql.Statement=DEBUG
 log4j.logger.java.sql.ResultSet=DEBUG
 log4j.logger.java.sql.PreparedStatement=DEBUG

3.配置log4j为日志的实现

 
    <settings>
         <setting name="logImpl" value="LOG4J"/>
     </settings>

 

4.log4j的使用,直接测试运行刚才的查询

 [org.apache.ibatis.logging.LogFactory]-Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
 [org.apache.ibatis.logging.LogFactory]-Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
 [org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
 [org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
 [org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
 [org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
 [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
 [org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 2007331442.
 [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@77a57272]
 [com.zhou.dao.UserMapper.getUserById]-==> Preparing: select * from mybatis.user where id = ?;
 [com.zhou.dao.UserMapper.getUserById]-==> Parameters: 1(Integer)
 [com.zhou.dao.UserMapper.getUserById]-<==     Total: 1
 User{id=1, name='zhou', password='123456'}
 [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@77a57272]
 [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@77a57272]
 [org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 2007331442 to pool.

简单使用

  1. 在要使用Log4j的类中导入包:import org.apache.log4j.Logger;

  2. 生成日志对象,参数为当前测试类的class:static Logger logger = Logger.getLogger(UserDaoTest.class);

  3. 日志级别

 @Test
 public void testLog4j(){
     logger.info("info:进入了testLog4j");
     logger.debug("info:进入了testLog4j");
     logger.error("error:进入了testLog4j");
 }

 

7、分页

为什么要分页?

减少数据的处理量

7.1、limit分页(推荐)

 SELECT * from user limit 0,5    #从0开始,查5个,[0,5)

添加接口

 
List<User4> getUserByLimit(Map<String,Integer> map);

 

添加映射,使用5.2的UserMap进行结果映射

 <select id="getUserByLimit" parameterType="map" resultMap="UserMap">
     select * from mybatis.user limit #{startIndex},#{pageSize}
 </select>

 

添加测试代码:

 
@Test
 public void getUserByLimit(){
     SqlSession sqlSession = MyBatisUtils.getSqlSession();
     UserMapper4 mapper = sqlSession.getMapper(UserMapper4.class);
 ​
     HashMap<String, Integer> map = new HashMap<>();
     map.put("startIndex",1);
     map.put("pageSize",2);
 ​
     List<User4> user4List = mapper.getUserByLimit(map);
     for (User4 user4 : user4List) {
         System.out.println(user4);
     }
 ​
     sqlSession.close();
 }

 

得到测试结果,查询从第2个记录开始(从0开始计数)的2个记录

User{id=2, name='rong', password='234567'} User{id=3, name='ming', password='345678'}

 [org.apache.ibatis.logging.LogFactory]-Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
 [org.apache.ibatis.logging.LogFactory]-Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
 [org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
 [org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
 [org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
 [org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections.
 [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
 [org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 1176735295.
 [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@46238e3f]
 [com.zhou.dao.UserMapper4.getUserByLimit]-==> Preparing: select * from mybatis.user limit ?,?
 [com.zhou.dao.UserMapper4.getUserByLimit]-==> Parameters: 1(Integer), 2(Integer)
 [com.zhou.dao.UserMapper4.getUserByLimit]-<==     Total: 2
 User{id=2, name='rong', password='234567'}
 User{id=3, name='ming', password='345678'}
 [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@46238e3f]
 [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@46238e3f]
 [org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 1176735295 to pool.

7.2、RowBounds分页

不再使用SQL实现分类

1.接口:

 List<User4> getUserByRowBounds();

 

2.mapper.xml

 <select id="getUserByRowBounds" resultMap="UserMap">
     select * from mybatis.user
 </select>

 

3.测试

 
@Test
 public void getUserByRowBounds(){
     SqlSession sqlSession = MyBatisUtils.getSqlSession();
 ​
     //RowBounds实现
     RowBounds rowBounds = new RowBounds(1, 2);
 ​
     //通过Java代码层面实现分类
     List<User4> userList = sqlSession.selectList("com.zhou.dao.UserMapper4.getUserByRowBounds",null,rowBounds);
 ​
     for (User4 user4 : userList) {
         System.out.println(user4);
     }
 ​
     sqlSession.close();
 }

 

7.3、分页插件

MyBatis分页插件PageHelper

了解即可,架构师有可能使用

https://pagehelper.github.io/

8、使用注解开发

8.1、面向接口编程思想

从面向对象编程转为面向接口编程

根本原因:解耦,可拓展,提高复用,分层开发中,上层不用管具体的实现,大家都遵循共同的标准,使得开发变得容易,规范性更好。

在一个面向对象的系统中,系统的各种功能是由许许多多不同对象协作完成的,在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了。

而各个对象之间的协作关系则成为系统设计的关键。小到不同类的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。

关于接口的理解

定义(规范,约束)与实现的分离

接口的本身反映了系统设计人员对系统的抽象理解。

接口应有两类:

  • 第一类是对一个个体的抽象,可以对应为一个抽象体(abstract class)

  • 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface)

  • 个体有可能有多个抽象面。

三个面向的区别

面向对象是指:考虑问题时,以对象为单位,考虑它的属性及方法

面向过程是指,考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现

接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题,更多的体现就是对系统整体的架构

用注解开发就是更好的使用面向接口编程的思想

8.2、使用注解处理简单操作

对于像 BlogMapper 这样的映射器类来说,还有另一种方法来完成语句映射。 它们映射的语句可以不用 XML 来配置,而可以使用 Java 注解来配置。

使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

本质:反射

底层:动态代理

使用反射获取UserMapper所有信息,动态获取一个对象的所有属性和方法

1.注解在接口上实现

 @Select("select * from user")
 List<User> getUsers();

 

2.在配置文件中绑定接口

 <mappers>
     <mapper class="com.zhou.dao.UserMapper"/>
 </mappers>

 

3.测试

 package com.zhou.dao;
 ​
 import com.zhou.pojo.User;
 import com.zhou.utils.MyBatisUtils;
 import org.apache.ibatis.session.SqlSession;
 import org.junit.Test;
 ​
 import java.util.List;
 ​
 public class UserDaoTest {
     @Test
     public void test(){
         SqlSession sqlSession = MyBatisUtils.getSqlSession();
         //底层主要应用反射
         UserMapper mapper = sqlSession.getMapper(UserMapper.class);
         List<User> users = mapper.getUsers();
         for (User user : users) {
             System.out.println(user);
         }
         sqlSession.close();
     }
 }

 

Mybatis详细的执行流程

  1. Resources获取加载全局配置文件

  2. 实例化SqlSessionFactoryBuilder构造器

  3. 解析配置文件流XMLConfigBuilder(配置文件报错会出现在这里)

  4. Configuration读取所有的配置信息

  5. 实例化SqlSessionFactory

  6. transactional事务管理器(含缓存)

  7. 创建执行器executor(核心)

  8. 创建sqlSession

  9. 实现CRUD(实现回滚)-->6

  10. 查看是否执行成功(实现回滚)-->6

  11. 提交事务

  12. 关闭

可以采用debug方式探究源码,变量参数是如何一步一步获取的

8.3、注解增删改查CRUD

可以在工具类创建的时候实现自动提交事务

将MyBatisUtils中的openSession增加参数true,开启自动提交事务

 
public static SqlSession getSqlSession(){
  return sqlSessionFactory.openSession(true);
 }

 

1.编写接口,增加注解

 package com.zhou.dao;
 ​
 import com.zhou.pojo.User;
 import org.apache.ibatis.annotations.*;
 ​
 import java.util.List;
 import java.util.Map;
 ​
 /**
  * 映射接口
  */
 public interface UserMapper {
 ​
     /**
      * 查所有用户
      * @return
      */
     @Select("select * from user")
     List<User> getUsers();
 ​
     /**
      * 有多个参数时,基本类型所有参数前必须加上@Param注解
      * @param id
      * @return
      */
     @Select("select * from user where id = #{id}")
     User getUserById(@Param("id") int id);
 ​
     /**
      * 增加用户
      * @param user
      * @return
      */
     @Insert("insert into user(id,name,pwd) values (#{id},#{name},#{password})")
     int addUser(User user);
 ​
     /**
      * 修改用户
      * @param user
      * @return
      */
     @Update("update user set name = #{name},pwd=#{password} where id = #{id}")
     int updateUser(User user);
 ​
     /**
      * 删除用户
      * @param id
      * @return
      */
     @Delete("delete from user where id = #{uid}")
     int deleteUser(@Param("uid") int id);
 }

 

2.绑定注册接口(注意)

 <mappers>
     <mapper class="com.zhou.dao.UserMapper"/>
 </mappers>

 

3.测试类

 
package com.zhou.dao;
 ​
 import com.zhou.pojo.User;
 import com.zhou.utils.MyBatisUtils;
 import org.apache.ibatis.session.SqlSession;
 import org.junit.Test;
 ​
 import java.util.List;
 ​
 public class UserDaoTest {
     @Test
     public void test(){
         SqlSession sqlSession = MyBatisUtils.getSqlSession();
         //底层主要应用反射
         UserMapper mapper = sqlSession.getMapper(UserMapper.class);
         List<User> users = mapper.getUsers();
         for (User user : users) {
             System.out.println(user);
         }
         sqlSession.close();
     }
 ​
     @Test
     public void test1(){
         SqlSession sqlSession = MyBatisUtils.getSqlSession();
         //底层主要应用反射
         UserMapper mapper = sqlSession.getMapper(UserMapper.class);
 ​
         User userById = mapper.getUserById(1);
         System.out.println(userById);
         sqlSession.close();
     }
 ​
     @Test
     public void test2(){
         SqlSession sqlSession = MyBatisUtils.getSqlSession();
 ​
         UserMapper mapper = sqlSession.getMapper(UserMapper.class);
 ​
         mapper.addUser(new User(5,"zhang","123123"));
 ​
         sqlSession.close();
     }
 ​
     @Test
     public void test3(){
         SqlSession sqlSession = MyBatisUtils.getSqlSession();
 ​
         UserMapper mapper = sqlSession.getMapper(UserMapper.class);
 ​
         mapper.updateUser(new User(5,"wang","231231"));
 ​
         sqlSession.close();
     }
 ​
     @Test
     public void test4(){
         SqlSession sqlSession = MyBatisUtils.getSqlSession();
 ​
         UserMapper mapper = sqlSession.getMapper(UserMapper.class);
 ​
         mapper.deleteUser(5);
 ​
         sqlSession.close();
     }
 }

 

注意:必须要将接口注册绑定到核心配置文件中

关于@Param()

  • 基本类型的参数或者String类型,需要加上

  • 引用类型不需要加

  • 如果只有一个基本类型可以忽略,但建议都加上

  • 在Sql里#{xxx}引用的就是@Param("xxx")设置的属性名

#{} 和 ${}的区别

预编译,#{}防止Sql注入,$不安全

9、Lombok(慎用)

偷懒专用!

https://projectlombok.org/

官网解释:

Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java. Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.

ProjectLombok是一个java库,可以自动插入编辑器和构建工具,提高java的性能。 不需要再编写getter、setter、toString或equals方法,使用一个注释,您的类就有了一个功能齐全的生成器,自动化了日志变量,等等。

使用步骤:

1.在IDEA中安装Lombok插件

2.在项目中导入Lombok的jar包

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

 

3.在实体类上加注解

@Getter and @Setter @FieldNameConstants @ToString @EqualsAndHashCode @AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor @Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog @Data(重点) @Builder @SuperBuilder @Singular @Delegate @Value @Accessors @Wither @With @SneakyThrows @val @var experimental @var @UtilityClass

@Data:无参构造、get、set、toString、hashcode、equals等方法

@AllArgsConstructor:有参构造(此时@Data无参构造消失)

@NoArgsConstructor:无参构造,和有参构造搭配使用

慎用!优缺点明显!需要灵活运用

公司用就用,如果公司不用尽量不用,因为你一用别的人也要跟着用

优点:

  1. 能通过注解的形式自动生成构造器,提高了一定的开发效率

  2. 让代码变得简洁,不用过多的关注相应的方法

  3. 属性做修改时,也简化了维护为这些属性生成的getter、setter方法等

缺点:

  1. 不支持多种参数构造器的重载

  2. 虽然省去手动创建大量方法的麻烦,但大大降低了源代码的可读性和完整性,降低了阅读源代码的舒适度

  3. 如果一些getter、setter方法需要补充内容时不适用

10、复杂查询环境搭建

10.1、设置外键

 CREATE TABLE `teacher` (
   `id` INT(10) NOT NULL,
   `name` VARCHAR(30) DEFAULT NULL,
   PRIMARY KEY (`id`)
 ) ENGINE=INNODB DEFAULT CHARSET=utf8;
 ​
 INSERT INTO teacher(`id`, `name`) VALUES (1, '周老师'); 
 ​
 CREATE TABLE `student` (
   `id` INT(10) NOT NULL,
   `name` VARCHAR(30) DEFAULT NULL,
   `tid` INT(10) DEFAULT NULL,
   PRIMARY KEY (`id`),
   KEY `fktid` (`tid`),
   CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
 ) ENGINE=INNODB DEFAULT CHARSET=utf8;
 INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1'); 
 INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1'); 
 INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1'); 
 INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1'); 
 INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');

 

10.2、测试环境搭建

1.导入lombok

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

 

2.新建实体类Teacher,Student,与数据库对应,外键单独作为对象

 @Data
 public class Student {
     private int id;
     private String name;
 ​
     /**
      * 学生需要关联一个老师
      */
     private Teacher teacher;
 }
 @Data
 public class Teacher {
     private int id;
     private String name;
 }

 

3.建立Mapper接口

 public interface TeacherMapper {
     @Select("select * from teacher where id = #{tid}")
     Teacher getTeacher(@Param("tid")int id);
 }

 

4.建立Mapper.xml文件

 <?xml version="1.0" encoding="UTF8" ?>
 <!DOCTYPE mapper
         PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
         "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.zhou.dao.TeacherMapper"></mapper>
 <?xml version="1.0" encoding="UTF8" ?>
 <!DOCTYPE mapper
         PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
         "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.zhou.dao.StudentMapper"></mapper>

 

5.在核心配置文件中绑定注册Mapper接口或者文件,db.properties与4.3相同

mapper导入方式很多

 <?xml version="1.0" encoding="UTF8" ?>
 <!DOCTYPE configuration
         PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
         "http://mybatis.org/dtd/mybatis-3-config.dtd">
 <configuration>
 <!--    引入外部配置文件-->
     <properties resource="db.properties"/><!--    标志的日志工厂实现-->
     <settings>
         <setting name="logImpl" value="STDOUT_LOGGING"/>
     </settings><typeAliases>
         <package name="com.zhou.pojo"/>
     </typeAliases><environments default="development">
         <environment id="development">
             <transactionManager type="JDBC"/>
             <dataSource type="POOLED">
                 <property name="driver" value="${driver}"/>
                 <property name="url" value="${url}"/>
                 <property name="username" value="${username}"/>
                 <property name="password" value="${password}"/>
             </dataSource>
         </environment>
     </environments>
 ​
 ​
     <mappers>
 <!--        <mapper resource="classpath:com/zhou/dao/*.xml"/>-->
 <!--        <mapper class="com.zhou.dao.TeacherMapper"/>-->
 <!--        <mapper class="com.zhou.dao.StudentMapper"/>-->
         <package name="com.zhou.dao"/>
     </mappers>
 </configuration>

 

6.测试查询是否能够成功

 
public class MyTest {
     public static void main(String[] args) {
         SqlSession sqlSession = MyBatisUtils.getSqlSession();
         TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
         Teacher teacher = mapper.getTeacher(1);
         System.out.println(teacher);
         sqlSession.close();
     }
 }

 

测试结果:

 Opening JDBC Connection
 Created connection 633070006.
 ==> Preparing: select * from teacher where id = ?
 ==> Parameters: 1(Integer)
 <==   Columns: id, name
 <==       Row: 1, 周老师
 <==     Total: 1
 Teacher(id=1, name=周老师)
 Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@25bbe1b6]
 Returned connection 633070006 to pool.

10.3、多对一处理

一个老师对应多个学生,需要查询一个老师的所有学生

10.3.1、按照查询嵌套处理

先将所有的学生信息都查出来,再在里边筛选对应该老师的学生,类似于子查询

 <?xml version="1.0" encoding="UTF8" ?>
 <!DOCTYPE mapper
         PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
         "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.zhou.dao.StudentMapper"><!--    思路:
     1.查询所有的学生信息
     2.根据查询出来的学生的tid,寻找对应的老师(类似子查询)
 --><select id="getStudent" resultMap="StudentTeacher">
         select * from mybatis.student;
     </select><resultMap id="StudentTeacher" type="Student">
         <result property="id" column="id"/>
         <result property="name" column="name"/>
 <!--        复杂的属性需要单独处理
             对象:association
             集合:collection
 -->
         <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
 <!--        <collection property="teacher"/>-->
     </resultMap><select id="getTeacher" resultType="Teacher">
         select * from teacher where id = #{id};
     </select></mapper>

 

10.3.2、按照结果嵌套处理(推荐,比较清晰)

先将所有需要的数据查询出来(可能在不同表内),再确定这些数据对应的关系

 <select id="getStudent2" resultMap="StudentTeacher2">
         select s.id sid, s.name sname, t.name tname
         from mybatis.student s,mybatis.teacher t
         where s.tid = t.id;
     </select><!--    查找完毕后,输出时确定关系-->
     <resultMap id="StudentTeacher2" type="Student">
         <result property="id" column="sid"/>
         <result property="name" column="sname"/>
         <association property="teacher" javaType="Teacher">
             <result property="name" column="tname"/>
         </association>
     </resultMap>

 

测试类

 @Test
 public void testStudent(){
     SqlSession sqlSession = MyBatisUtils.getSqlSession();
 ​
     StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
 ​
     List<Student> studentList = mapper.getStudent();
     //List<Student> studentList = mapper.getStudent2();
for (Student student : studentList) {
         System.out.println(student);
     }
 ​
     sqlSession.close();
 }

 

结果:

输出多个学生

 Opening JDBC Connection
 Created connection 802581203.
 ==> Preparing: select * from mybatis.student;
 ==> Parameters:
 <==   Columns: id, name, tid
 <==       Row: 1, 小明, 1
 ====> Preparing: select * from teacher where id = ?;
 ====> Parameters: 1(Integer)
 <====   Columns: id, name
 <====       Row: 1, 周老师
 <====     Total: 1
 <==       Row: 2, 小红, 1
 <==       Row: 3, 小张, 1
 <==       Row: 4, 小李, 1
 <==       Row: 5, 小王, 1
 <==     Total: 5
 Student(id=1, name=小明, teacher=Teacher(id=1, name=周老师))
 Student(id=2, name=小红, teacher=Teacher(id=1, name=周老师))
 Student(id=3, name=小张, teacher=Teacher(id=1, name=周老师))
 Student(id=4, name=小李, teacher=Teacher(id=1, name=周老师))
 Student(id=5, name=小王, teacher=Teacher(id=1, name=周老师))
 Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@2fd66ad3]
 Returned connection 802581203 to pool.

回顾MySQL多对一查询方式:

  • 子查询:getStudent

  • 联表查询:getStudent2

10.4、一对多处理

一个老师拥有多个学生,想要查询这个老师

实体类

 @Data
 public class Student {
     private int id;
     private String name;
     private int tid;
 }
 @Data
 public class Teacher {
     private int id;
     private String name;
 ​
     /**
      * 一个老师拥有多个学生
      */
     private List<Student> students;
 }

 

核心映射文件

 <?xml version="1.0" encoding="UTF8" ?>
 <!DOCTYPE mapper
         PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
         "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.zhou.dao.TeacherMapper"><!--        按查询嵌套处理-->
 <!--目的,查老师-->
     <select id="getTeacher" resultMap="TeacherStudent2">
         select * from mybatis.teacher where id = #{tid};
     </select>
 <!--List<Student> students对应javaType<ofType> property select:子查询 column:需要传递的参数tid-->
     <resultMap id="TeacherStudent2" type="Teacher">
         <collection property="students" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId" column="tid"/>
     </resultMap><select id="getStudentByTeacherId" resultType="Student">
         select * from mybatis.student where tid = #{tid};
     </select><!--    =====================================--><!--    按结果嵌套处理-->
     <select id="getTeacher2" resultMap="TeacherStudent">
         select s.id sid, s.name sname, t.name tname, t.id tid
         from mybatis.student s,mybatis.teacher t
         where s.tid = t.id and t.id = #{tid};
     </select><resultMap id="TeacherStudent" type="Teacher">
         <result property="id" column="id"/>
         <result property="name" column="tname"/>
         <!--
                     collection:集合 javaType;指定属性的类型
                     如果为集合中的泛型信息,不能精确的定位属性,需要使用ofType获取泛型中的约束类型
         -->
         <collection property="students" ofType="Student">
             <result property="id" column="sid"/>
             <result property="name" column="sname"/>
             <result property="tid" column="tid"/>
         </collection>
     </resultMap></mapper>

 

测试类

 
@Test
 public void test1(){
     SqlSession sqlSession = MyBatisUtils.getSqlSession();
 ​
     TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
 ​
     Teacher teacher = mapper.getTeacher(1);
     //Teacher teacher = mapper.getTeacher2(1);
 ​
     System.out.println(teacher);
 ​
     sqlSession.close();
 }

 

查询结果

按照查询嵌套处理

 Opening JDBC Connection
 Created connection 1883840933.
 ==> Preparing: select s.id sid, s.name sname, t.name tname, t.id tid from mybatis.student s,mybatis.teacher t where s.tid = t.id and t.id = ?;
 ==> Parameters: 1(Integer)
 <==   Columns: sid, sname, tname, tid
 <==       Row: 1, 小明, 周老师, 1
 <==       Row: 2, 小红, 周老师, 1
 <==       Row: 3, 小张, 周老师, 1
 <==       Row: 4, 小李, 周老师, 1
 <==       Row: 5, 小王, 周老师, 1
 <==     Total: 5
 Teacher(id=0, name=周老师, students=[Student(id=1, name=小明, tid=1), Student(id=2, name=小红, tid=1), Student(id=3, name=小张, tid=1), Student(id=4, name=小李, tid=1), Student(id=5, name=小王, tid=1)])
 Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@704921a5]
 Returned connection 1883840933 to pool.

teacher id为0

10.5、小结

  • association:关联 多对一

  • collection:集合 一对多

  • JavaType:用来指定实体类中属性的类型

  • ofType:用来指定映射到List或者集合中的pojo类型,泛型中的约束类型

注意点:

  • 保证SQL的可读性,尽量保证通俗易懂

  • 注意一对多和多对一中属性名和字段的问题

  • 如果问题不好排查错误,可以使用体质,建议使用Log4j

11、动态SQL

有些条件有可能生效,有可能不生效,不是固定的SQL语句

根据不同的条件生成不同的SQL语句

  • if

  • choose (when, otherwise)

  • trim (where, set)

  • foreach

11.1、搭建环境

 CREATE TABLE `blog`(
 `id` VARCHAR(50) NOT NULL COMMENT '博客id',
 `title` VARCHAR(100) NOT NULL COMMENT '博客标题',
 `author` VARCHAR(30) NOT NULL COMMENT '博客作者',
 `create_time` DATETIME NOT NULL COMMENT '创建时间',
 `views` INT(30) NOT NULL COMMENT '浏览量'
 )ENGINE=INNODB DEFAULT CHARSET=utf8

 

创建一个基础工程

  1. 导包Lombok

     <dependencies>
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
             <version>1.18.16</version>
         </dependency>
     </dependencies>

     

  2. 编写配置文件db.properties、mybatis-config.xml

     driver=com.mysql.jdbc.Driver
     url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=GMT%2B8
     username=root
     password=root
     <?xml version="1.0" encoding="UTF8" ?>
     <!DOCTYPE configuration
             PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
             "http://mybatis.org/dtd/mybatis-3-config.dtd">
     <configuration>
     <!--    引入外部配置文件-->
         <properties resource="db.properties"/><!--    标志的日志工厂实现-->
         <settings>
             <setting name="logImpl" value="STDOUT_LOGGING"/>
     <!--        是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。-->
             <setting name="mapUnderscoreToCamelCase" value="true"/>
         </settings><typeAliases>
             <package name="com.zhou.pojo"/>
         </typeAliases><environments default="development">
             <environment id="development">
                 <transactionManager type="JDBC"/>
                 <dataSource type="POOLED">
                     <property name="driver" value="${driver}"/>
                     <property name="url" value="${url}"/>
                     <property name="username" value="${username}"/>
                     <property name="password" value="${password}"/>
                 </dataSource>
             </environment>
         </environments>
     ​
     ​
         <mappers>
             <!--        <mapper resource="classpath:com/zhou/dao/*.xml"/>-->
                     <mapper class="com.zhou.dao.BlogMapper"/>
             <!--        <mapper class="com.zhou.dao.StudentMapper"/>-->
             <!--        <package name="com.zhou.dao"/>-->
         </mappers>
     </configuration>

     

  3. 编写实体类和工具类IDUtils

     @Data
     public class Blog {
         private String id;
         private String title;
         private String author;
         private Date createTime;        //属性名和字段名create_time不一致
         private int views;
     }
     public class IDUtils {
         public static String getId(){
             return UUID.randomUUID().toString().replaceAll("-","");
         }
     }

     

  1. 编写实体类对应Mapper接口和Mapper.xml文件

  2. 测试写入数据

     <?xml version="1.0" encoding="UTF8" ?>
     <!DOCTYPE mapper
             PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
             "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
     <mapper namespace="com.zhou.dao.BlogMapper"><insert id="addBlog" parameterType="Blog">
             insert into mybatis.blog(id, title, author, create_time, views)
             values (#{id},#{title},#{author},#{createTime},#{views});
         </insert></mapper>

     

11.2、if

where后必须要有条件,true做顶替使用

 <select id="queryBlogIf" parameterType="map" resultType="Blog">
     select * from mybatis.blog where true
     <if test="title != null">
         and title = #{title}
     </if>
     <if test="author != null">
         and author = #{author}
     </if>
 </select>

 

测试类:以下使用类似

 @Test
 public void queryBlogIfTest(){
     SqlSession sqlSession = MyBatisUtils.getSqlSession();
     BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
     HashMap map = new HashMap();
 ​
     //map.put("title","Java");
     map.put("author","zhou");
 ​
     List<Blog> blogs = mapper.queryBlogIf(map);
 ​
     for (Blog blog : blogs) {
         System.out.println(blog);
     }
 ​
     sqlSession.close();
 }

 

11.3、choose (when, otherwise)

choose 元素,它有点像 Java 中的 switch 语句

 <select id="queryBlogChoose"
      resultType="Blog">
   SELECT * FROM blog WHERE state = ‘ACTIVE’
   <choose>
     <when test="title != null">
       AND title like #{title}
     </when>
     <when test="author != null">
       AND author like #{author}
     </when>
     <otherwise>
       AND views = #{views}
     </otherwise>
   </choose>
 </select>

 

11.4、trim(where,set)

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。防止出现where后无条件或有 “AND” 或 “OR”

 <select id="queryBlogWhere"
      resultType="Blog">
   SELECT * FROM blog
   <where>
     <if test="state != null">
          state = #{state}
     </if>
     <if test="title != null">
         AND title like #{title}
     </if>
     <if test="author != null and author.name != null">
         AND author_name like #{author.name}
     </if>
   </where>
 </select>

 

组合效果:where可以智能识别开头的and

 <select id="queryBlogChoose"
      resultType="Blog">
   SELECT * FROM blog 
     <where>
         <choose>
             <when test="title != null">
                 title like #{title}
             </when>
             <when test="author != null">
                 AND author like #{author}
             </when>
             <otherwise>
                 AND views like #{views}
             </otherwise>
         </choose>
     </where>
 </select>

 

update对应的set可以用于动态包含需要更新的列,忽略其它不更新的列,忽略逗号

set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)

 <update id="updateBlog" parameterType="map">
     update mybatis.blog
     <set>
         <if test="title!=null">
             title = #{title},
         </if>
         <if test="author!=null">
             author = #{author}
         </if>
     </set>
     where id = #{id}
 </update>

 

通过自定义 trim 元素来定制 where 元素的功能。检测哪些部分是可以忽略删掉的(分前缀、后缀……)比如,和 where 元素等价的自定义 trim 元素为:

prefixOverrides前缀

 <trim prefix="WHERE" prefixOverrides="AND |OR ">
   ...
 </trim>

 

set 元素等价的自定义 trim 元素

suffixOverrides后缀

 <trim prefix="SET" suffixOverrides=",">
   ...
 </trim>

 

所谓的动态SQL,本质还是SQL语句,只是我们可以在SQL层面,去执行一个逻辑代码

11.5、foreach

对集合进行遍历(尤其是在构建 IN 条件语句的时候)

指定一个集合collection,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符

open开始,separator分隔符,close闭合

 select * from user where 1=1 and (id=1 or id=2 or id=3)

转换为

 <foreach item="id" index="index" collection="ids"
       open="(" separator="or" close=")">
         #{id}
 </foreach>

 

11.6、SQL片段

可以实现代码片段复用,将一些公共部分抽取出来

使用sql标签抽取公共的部分

在需要使用的地方使用include标签引用

上述update语句可以改写为

 <update id="updateBlog" parameterType="map">
     update mybatis.blog
     <set>
         <include refid="if-title-author"/>
     </set>
     where id = #{id}
 </update><sql id="if-title-author">
     <if test="title!=null">
         title = #{title},
     </if>
     <if test="author!=null">
         author = #{author}
     </if>
 </sql>

 

注意事项:

  • 最好基于单表定义SQL字段

  • 不要存在where、set标签

动态SQL就是在拼接SQL语句,只要保证SQL的正确性,按照SQL的格式,进行排列组合

建议:先在MySQL中写出完整的SQL语句,再对应的修改成为动态SQL实现

12、缓存

12.1、简介

查询连接数据库耗资源

一次查询的结果,给它暂存在一个可以直接取到的地方,如内存:这个地方叫缓存

再次查询相同数据的时候,直接走缓存,就不用走数据库了

  1. 什么是缓存(Cache)?

    1. 存在内存中的临时数据

    2. 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题

  2. 为什么使用缓存?

    1. 减少和数据库的交互次数,减少系统开销,提高系统效率

  3. 什么样的数据能使用缓存?

    1. 经常查询并且不经常改变的数据

12.2、MyBatis缓存

MyBatis包含一个非常强大的查询缓存特性,可以非常方便地定制和配置缓存,缓存可以极大的提升查询效率

默认定义了两级缓存:一级缓存和二级缓存

  • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,称为本地缓存)

  • 二级缓存需要手动开启和配置,基于namespace级别的缓存

  • 为了提高扩展性,MyBatis定义了缓存接口Cache,可以通过实现Cache接口自定义二级缓存

12.3、一级缓存

一级缓存也叫本地缓存:SqlSession

  • 与数据库同一次会话期间查询到的数据会放在本地缓存中

  • 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库

测试步骤:

1.开启日志

2.测试在一个Session中查询两次记录

 
@Test
 public void test(){
     SqlSession sqlSession = MyBatisUtils.getSqlSession();
 ​
     UserMapper mapper = sqlSession.getMapper(UserMapper.class);
 ​
     User user1 = mapper.queryUserById(1);
     System.out.println(user1);
     System.out.println("==================================");
     User user2 = mapper.queryUserById(1);
     System.out.println(user2);
 ​
     System.out.println(user1==user2);
 ​
     sqlSession.close();
 }

 

测试结果:select只需要查一次

 Opening JDBC Connection
 Created connection 156545103.
 ==> Preparing: select * from mybatis.user where id = ?;
 ==> Parameters: 1(Integer)
 <==   Columns: id, name, pwd
 <==       Row: 1, zhou, 123456
 <==     Total: 1
 User(id=1, name=zhou, pwd=123456)
 ==================================
 User(id=1, name=zhou, pwd=123456)
 true
 Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@954b04f]
 Returned connection 156545103 to pool.

使用update/delete时,需要先查再改/删再查,所以需要查两次

缓存失效的情况:

  1. 查询不同的东西

  2. 增删改操作,会改变原来的数据,所以需要刷新缓存,保持数据库的一致性

  3. 查询不同的Mapper.xml

  4. 手动清理缓存 sqlSession.clearCache();

小结:一级缓存默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间段

一级缓存就是于一个Map

12.4、二级缓存

二级缓存也叫全局缓存,一级缓存作用域较低,所以诞生了二级缓存

基于namespace级别的缓存,一个名称空间,对应一个二级缓存

工作机制:

  • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中

  • 如果当前会话关闭了,这个会话对应的一级缓存就没了;会话关闭了,一级缓存中的数据被保存到二级缓存中

  • 新的会话查询信息,就可以从二级缓存中获取内容

  • 不同的mapper查出的数据会放在自己对应的缓存(map)中

步骤:

1.开启全局缓存

 <!--        显示的开启全局缓存(默认开启)-->
         <setting name="cacheEnabled" value="true"/>

 

2.在要使用二级缓存的Mapper中开启

 <cache/>

 

也可以自定义参数

 <!--    在当前Mapper.xml中使用二级缓存-->
     <cache
             eviction="FIFO"
             flushInterval="60000"
             size="512"
             readOnly="true"/>

 

3.测试

Error serializing object. Cause: java.io.NotSerializableException: com.zhou.pojo.User 需要序列化

 public class User implements Serializable {
     private int id;
     private String name;
     private String pwd;
 }
 @Test
 public void test(){
     SqlSession sqlSession1 = MyBatisUtils.getSqlSession();
     SqlSession sqlSession2 = MyBatisUtils.getSqlSession();
 ​
     UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
     User user1 = mapper1.queryUserById(1);
     System.out.println(user1);
     sqlSession1.close();
 ​
     UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
     User user2 = mapper2.queryUserById(1);
     System.out.println(user2);
 ​
     System.out.println(user1==user2);
 ​
     sqlSession2.close();
 }

 

在执行完第一个Session后再在第二个Session中调用同一个查询方法

输出结果:只调用了一次查询,第二次直接从二级缓存中取出

 Cache Hit Ratio [com.zhou.dao.UserMapper]: 0.0
 Opening JDBC Connection
 Created connection 1545087375.
 ==> Preparing: select * from mybatis.user where id = ?;
 ==> Parameters: 1(Integer)
 <==   Columns: id, name, pwd
 <==       Row: 1, zhou, 123456
 <==     Total: 1
 User(id=1, name=zhou, pwd=123456)
 Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@5c18298f]
 Returned connection 1545087375 to pool.
 Cache Hit Ratio [com.zhou.dao.UserMapper]: 0.5
 User(id=1, name=zhou, pwd=123456)
 false

小结:

只要开启了二级缓存,在同一个Mapper下就有效

所有的数据都会先放在一级缓存中

只有当会话提交,或者关闭的时候,才会提交到二级缓冲中

12.5、缓存原理

缓存顺序:

先看二级缓存(作用域为整个namespace)

再看一级缓存(作用域为所在Session)

再看数据库

 @Test
 public void test(){
     SqlSession sqlSession1 = MyBatisUtils.getSqlSession();
     SqlSession sqlSession2 = MyBatisUtils.getSqlSession();
 ​
     UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
     User user1 = mapper1.queryUserById(1);
     System.out.println(user1);
     sqlSession1.close();
     
     System.out.println("==========================");
 ​
     UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
     User user2 = mapper2.queryUserById(1);
     System.out.println(user2);
 ​
     User user3 = mapper2.queryUserById(2);
     System.out.println(user2);
     System.out.println(user3);
 ​
     User user4 = mapper2.queryUserById(2);
     System.out.println(user4);
 ​
     sqlSession2.close();
 }

 

查询结果:

 Cache Hit Ratio [com.zhou.dao.UserMapper]: 0.0
 Opening JDBC Connection
 Created connection 1545087375.
 ==> Preparing: select * from mybatis.user where id = ?;
 ==> Parameters: 1(Integer)
 <==   Columns: id, name, pwd
 <==       Row: 1, zhou, 123456
 <==     Total: 1
 
 User(id=1, name=zhou, pwd=123456)
 
 Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@5c18298f]
 Returned connection 1545087375 to pool.
 ==========================
 Cache Hit Ratio [com.zhou.dao.UserMapper]: 0.5
 
 User(id=1, name=zhou, pwd=123456)
 
 Cache Hit Ratio [com.zhou.dao.UserMapper]: 0.3333333333333333
 Opening JDBC Connection
 Checked out connection 1545087375 from pool.
 ==> Preparing: select * from mybatis.user where id = ?;
 ==> Parameters: 2(Integer)
 <==   Columns: id, name, pwd
 <==       Row: 2, rong, 234567
 <==     Total: 1
 
 User(id=1, name=zhou, pwd=123456)
 User(id=2, name=rong, pwd=234567)
 
 Cache Hit Ratio [com.zhou.dao.UserMapper]: 0.25
 
 User(id=2, name=rong, pwd=234567)
 
 Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@5c18298f]
 Returned connection 1545087375 to pool.

第一次查1,一级二级缓存都没有数据,调用数据库查询,数据写入二级缓存,第一个Session结束

第二次查1,在二级缓存中读到之前存的数据,无需调用数据库查1

第一次查2,没有数据,调用数据库查2,2写入一级缓存后,整体再读一级缓存,按照顺序先出之前写好的1后出2

后续类似

12.6、未来的缓存走Redis数据库

 

 

posted on 2021-08-23 23:54  zrm0612  阅读(24)  评论(0)    收藏  举报