尚硅谷-MyBatis3.5.7-非零基础-笔记篇-01

为什么是非零基础呢?因为本笔记不包括一些概念介绍、历史介绍、还有持久化层技术的对比了,概念那部分对我们直接去开发、或者说想快速做一些项目的童鞋是没有很大必要了吧,我们要的就是实践出真知!!!

官方-用户使用手册

看了下截止到目前MyBatis的最新版本是3.5.16,而尚硅谷发布MyBatis3.5.7零基础入门教程视频是2022年2月,最新版本的压缩包里就没有MyBatis法宝了,这里只能奉上3.5.7的法宝了。

那就是:官方发布的用户使用手册

借花献佛:通过网盘分享的文件:mybatis-3.5.7.pdf
链接: https://pan.baidu.com/s/1uI2gupmRiKVsFWWqbPg42A?pwd=gq52 提取码: gq52

正式开练 

教程开发环境

开发中很多问题可能都是环境配置不一致导致的,这里尚硅谷老师给出的教程环境列一下:

Idea2019、maven3.5.4、mysql5.7、jdk1.8

Maven – Maven Releases Historymaven与jdk的版本对照关系可参考:Maven – Maven Releases History

MyBatis初识

这里演示一下通过MyBatis实现增删改操作,这部分官方用户手册写得非常清楚了,一步一步怎么获取sqlSession、获取mapper接口实现类对象的。

准备实体类和数据库实体表

package com.coffeeship.pojo;

/**
 * @description: 实体类
 * @author: coffeeship
 * @date: 2024/10/24 15:01
 */
public class User {
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    private String sex;
    private String email;

    public User() {
    }

    public User(Integer id, String username, String password, Integer age, String sex, String email) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.age = age;
        this.sex = sex;
        this.email = email;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                ", email='" + email + '\'' +
                '}';
    }
}

导入依赖

<!-- Mybatis核心 -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
</dependency>

从XML配置文件获取SqlSessionFactory

对应测试类

SqlSession代表和数据库的一次会话,用完必须释放。

SqlSession和Connection一样都是非线程安全的,不能作为成员变量,否则会产生资源竞争。每次使用都应该去获取新的对象。

@Test
public void testMyBatisInsert() throws IOException {

    //加载核心配置文件
    InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
    //获取sqlSessionFactoryBuilder
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    //获取sqlSessionFactory
    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
    //获取SqlSession 默认不自动提交事务
//        SqlSession sqlSession = sqlSessionFactory.openSession();
    //设置事务自动提交
    SqlSession sqlSession = sqlSessionFactory.openSession(true);
    //获取Mapper对象 底层技术:代理
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    int result = mapper.insertUser();

    //手动提交事务
//        sqlSession.commit();
    System.out.println(result);
}

核心配置文件

包括数据库连接环境(事务管理器、数据连接池)

        transactionManager可选项:JDBC | MANAGED

        dataSource可选项:POOLED | UNPOOLED | JNDI

关于核心配置文件中的标签介绍,更详细的可以参考尚硅谷在2018年发布的mybatis教程。

        数据库连接池中的配置信息可通过properties文件注参;eg.

mapper 

引入dtd约束:http://mybatis.org/dtd/mybatis-3-config.dtd

<?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">
            <!--事务管理器,JDBC:最原始的方式,事务的操作回滚需要手动配置-->
            <transactionManager type="JDBC"/>
            <!--数据库连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:13306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="qts0922"/>
            </dataSource>
        </environment>
    </environments>
    <!--引入映射文件  来自源根的路径-->
    <mappers>
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>
</configuration>

 编写Mapper XML文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace等于接口类的全限定名-->
<mapper namespace="com.coffeeship.mapper.UserMapper">

    <insert id="insertUser">
        insert into t_user values(null, 'admin', '123456', 18, '女', 'zh091699@163.com')
    </insert>
    <update id="updateUser">
        update t_user set username = 'administrator' where id = 2;
    </update>
    <delete id="deleteUser">
        delete from t_user where id = 3
    </delete>
</mapper>

执行测试类中的方法

然后查询部分,涉及到返回对象,可以参考spring对JDBC的封装,即JdbcTemplate,就是需要在XML配置文件中指定一下resultType或resultMap,就是一点点不一样哈。比如:

<!--指定SQL返回类型
resultType:设置默认的映射关系
resultMap:设置自定义的映射关系
-->
<select id="getUserById" resultType="com.coffeeship.pojo.User">
    select * from t_user where id = 2
</select>
<select id="getAllUser" resultType="com.coffeeship.pojo.User">
    select * from t_user
</select>
@Test
public void testMyBatisSelectOne() throws IOException {
    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession(true);
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.getUserById();
    System.out.println(user);
}

@Test
public void testMyBatisSelectAll() throws IOException {
    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession(true);
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> allUser = mapper.getAllUser();
    allUser.forEach(user -> System.out.println(user));
}

 设置别名

typeAliases标签

 MyBatis核心配置文件中的标签顺序

The content of element type "configuration" must match "(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)".

mybatis-config.xml :类型别名不区分大小写

type:需要设置别名的类的全路径

alias属性值可选,若设置了以设置的值为准,不区分大小写;

若未设置,则默认是类名,不区分大小写

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

 UserMapper.xml

<!--    <select id="getUserById" resultType="com.coffeeship.pojo.User">
    select * from t_user where id = 2
</select>-->
<!--设置别名后...-->
<select id="getUserById" resultType="user">
    select * from t_user where id = 2
</select>
<!--    <select id="getAllUser" resultType="com.coffeeship.pojo.User">
    select * from t_user
</select>-->
<!--设置别名后...-->
<select id="getAllUser" resultType="user">
    select * from t_user
</select>

以包为单位设置默认别名,即类名、不区分大小写

<typeAliases>
<!--        <typeAlias type="com.coffeeship.pojo.User" alias="user"/>-->
    <package name="com.coffeeship.pojo"/>
</typeAliases>

以包为单位引入映射文件

<!--引入映射文件  来自源根的路径-->
<mappers>
    <!--<mapper resource="mappers/UserMapper.xml"/>-->
    <!--以包为单位引入映射文件
    要求: 1.mapper接口所在的包要和映射文件所在的包一致
    2.mapper接口要和映射文件的名字一致
    -->
    <package name="com.coffeeship.mapper"/>
</mappers>

MyBatis获取参数值的两种方式(重点)

MyBatis获取参数值的两种方式:${}#{}     (JDBC原生方式)

${}的本质就是字符串拼接,#{}的本质就是占位符赋值

${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号;但是#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,可以自动添加单引号。

${}、#{}可通过任意名称赋值,获取参数只与参数位置有关,但建议见名知意。

分别对应JDBC Template中的字符串拼接(有SQL注入的风险)和占位符赋值:?

单个字面量类型的参数

mapper接口中的方法参数为单个的字面量类型,此时可以使用${}#{}

以任意的名称获取参数的值,注意${}需要手动加单引号。

多个字面量类型的参数

mapper接口中的方法参数为多个时,此时MyBatis会自动将这些参数放在一个map集合中,

arg0,arg1...为键,以参数为值;

以param1,param2...为键,以参数为值;

因此只需要通过${}#{}访问map集合的键就可以获取相对应的值,

注意${}需要手动加单引号。

map集合类型的参数

mapper接口中的方法需要的参数为多个时,此时可以手动创建map集合,

将这些数据放在map中,只需要通过${}#{}访问map集合的键就可以获取相对应的值,

注意${}需要手动加单引号。

实体类类型的参数

mapper接口中的方法参数为实体类对象时,此时可以使用${}和#{}

通过访问实体类对象中的属性名获取属性值,

注意${}需要手动加单引号。

使用@Param标识参数

可以通过@Param注解标识mapper接口中的方法参数,

此时,会将这些参数放在map集合中,

@Param注解的value属性值为键,以参数为值;

以 param1,param2...为键,以参数为值;

只需要通过${}#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号。


UserMapper接口

public interface UserMapper {

    User getUserById(Integer id);

    User getUserByNamePwd(String username, String password);

    User getUserByNameAge(Map<String, Object> map);

    User getUser(User user);

    User getUserByParam(@Param("paramName") String username, @Param("paramPwd") String password);
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace等于接口类的全限定名-->
<mapper namespace="com.coffeeship.mapper.UserMapper">

    <!--单个字面量的参数,可以以任意名称获取参数的值,但建议”见名知意“-->
    <select id="getUserById" resultType="com.coffeeship.pojo.User">
        <!--select * from t_user where id = #{id}-->
        select * from t_user where id = #{anyName}
    </select>

    <!--多个字面量的参数,以arg0,arg1...为键或以param1,param2...为键-->
    <!--Parameter 'username' not found. Available parameters are [arg1, arg0, param1, param2]-->
    <select id="getUserByNamePwd" resultType="com.coffeeship.pojo.User">
        <!--错误写法:select * from t_user where username = #{username} and password = #{password}-->
        <!--以下均为正确写法-->
        <!--select * from t_user where username = #{arg0} and password = #{arg1}-->
        <!--select * from t_user where username = #{param1} and password = #{param2}-->
        select * from t_user where username = #{arg0} and password = #{param2}
    </select>

    <!--map集合类型的参数,访问map集合的键就可以获取相对应的值-->
    <select id="getUserByNameAge" resultType="com.coffeeship.pojo.User">
        select * from t_user where username = #{name} and age = #{age}
    </select>

    <!--实体类类型的参数,访问实体类对象中的属性名获取属性值-->
    <select id="getUser" resultType="com.coffeeship.pojo.User">
        select * from t_user where id = #{id} and username = #{username}
    </select>

    <!--使用@Param标识参数,以@Param注解的value属性值为键或以 param1,param2...为键-->
    <select id="getUserByParam" resultType="com.coffeeship.pojo.User">
        <!--select * from t_user where username = #{paramName} and password = #{paramPwd}-->
        <!--select * from t_user where username = #{param1} and password = #{param2}-->
        select * from t_user where username = #{paramName} and password = #{param2}
    </select>


</mapper>

工具类

public class SqlSessionUtil {
    public static SqlSession getSqlSession() {
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        return sqlSession;
    }
}

测试类

public class ParamTest {
    
    @Test
    public void test01() {
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.getUserById(2);
        System.out.println(user);
    }

    @Test
    public void test02() {
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.getUserByNamePwd("admin", "123456");
        System.out.println(user);
    }

    @Test
    public void test03() {
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Map<String, Object> map = new HashMap<>();
        map.put("name", "admin");
        map.put("age", 18);
        User user = mapper.getUserByNameAge(map);
        System.out.println(user);
    }

    @Test
    public void test04() {
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = new User(2, "administrator", "123456", 18, "女", "zh091699@163.com");
        User returnUser = mapper.getUser(user);
        System.out.println(returnUser);
    }

    @Test
    public void test05() {
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.getUserByParam("admin", "123456");
        System.out.println(user);
    }
}

@Param源码解析

User getUserByParam(@Param("paramName") String username, @Param("paramPwd") String password);
<!--使用@Param标识参数,以@Param注解的value属性值为键或以 param1,param2...为键-->
<select id="getUserByParam" resultType="com.coffeeship.pojo.User">
    <!--select * from t_user where username = #{paramName} and password = #{paramPwd}-->
    <!--select * from t_user where username = #{param1} and password = #{param2}-->
    select * from t_user where username = #{paramName} and password = #{param2}
</select>

从这里进入

 mybatis创建了接口的实现类代理对象——接口式编程

原生:DAO接口、DAOImpl接口实现类

mybatis:mapper、mapper.xml

进入cachedInvoker的invoke方法

进入execute,匹配到当前是SELECT类型 

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

先看参数映射关系是如何放到map中的

再来看sql执行过程

MappedStatement对象中有属性statementType,属性值为PREPARED,表示预编译的;resultMaps,有一个值就是Mapper接口方法中指明的返回值User类;还有就是sqlCommandType,SQL的操作类型,即SELECT。

获取BoundSql对象,包含属性:1.解析后的sql,可以看到原生sql是用的?表示占位符

2.parameterMappings,记录占位符处对应的映射关系

3.parameterObject, 传入的参数及对应的参数值;键与parameterMappings中的property对应

委托代理,返回一个CacheKey

通过CacheKey 来封装缓存的 key 值,CacheKey 可以封装多个影响缓存项的因素。

缓存的作用就是提高查询速度,数据库中的sql也有对应的缓存机制,当你使用绑定变量的时候,判断sql执行的频率,可以考虑将sql缓存起来,也可以类比java中的热代码使用JIT编译一样。

将热点SQL缓存起来,下次只是改变传参而已,sql其实没变,这样查询速率就会很快。 

又到了激动人心的环节

这里MappedStatement对象中的cache是null,注意这里的cache与上面的CacheKey不是一回事!那MappedStatement里的这些属性都是啥意思呢?

可以参考这篇博主的介绍,cache是二级缓存策略,默认就是null。

https://juejin.cn/post/6886805132936216583

建立数据库连接,并通过反射执行了autoCommit的设置,再通过反射执行了preparedStatement方法,这里熟悉JDBC原生方式访问数据库的童鞋就知道了。

@Override
  public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, params);
      }
      if (EXECUTE_METHODS.contains(method.getName())) {
        if (isDebugEnabled()) {
          debug("Parameters: " + getParameterValueString(), true);
        }
        clearColumnInfo();
        if ("executeQuery".equals(method.getName())) {
          ResultSet rs = (ResultSet) method.invoke(statement, params);
          return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
        } else {
          return method.invoke(statement, params);
        }
      } else if (SET_METHODS.contains(method.getName())) {
        if ("setNull".equals(method.getName())) {
          setColumn(params[0], null);
        } else {
          setColumn(params[0], params[1]);
        }
        return method.invoke(statement, params);
      } else if ("getResultSet".equals(method.getName())) {
        ResultSet rs = (ResultSet) method.invoke(statement, params);
        return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
      } else if ("getUpdateCount".equals(method.getName())) {
        int updateCount = (Integer) method.invoke(statement, params);
        if (updateCount != -1) {
          debug("   Updates: " + updateCount, false);
        }
        return updateCount;
      } else {
        return method.invoke(statement, params);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

method.invoke(statement, params)终于要通过反射给sql赋值了

赋完值后再看SQL语句

再赋第2个参数 

Executor接口的实现类对象delegate执行。 

这里就很熟悉了 ps.execute()也是通过反射去执行

resultSetHandler.handleResultSets(ps)应该是在封装返回结果

 好吧,这篇笔记就到这里结束吧,不然都看困了。。。

扒源码的路漫漫,其修远兮,吾将上下而求索~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值