Mybatis入门の基础操作

1 Mybatis概述

   MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。

   mybatis是一个优秀的基于java的持久层框架,它内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。 

   mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。 

   采用ORM思想解决了实体和数据库映射的问题,对jdbc进行了封装,屏蔽了jdbc api底层访问细节,使我们不用与jdbc api打交道,就可以完成对数据库的持久化操作。

mybatis技术曾经做过"更名",早期这个团队将技术框架称为"iBatis",mybatis研发团队"跳槽"google,从google跳"githup",将之前"ibatis"改为"mybaits"

mybatis中文网站:https://mybatis.net.cn/

2 mybatis框架技术定位

SSM框架与三层架构对应关系: Java提供JDBC,Spring提供SpringJdbc,mybatis

3 Mybatis的优势:传统jdbc存在的问题

JDBC操作步骤:

1、注册驱动:Class.forName("com.mysql.jdbc.Driver")

2、获取连接:Connection connection=DriverManager.getConnection(url,username,password);

3、获取预处理对象:PreparedStatement pstm=connection.PreparedStatement("sql");

4、执行sql操作:Result result=pstm.ExecuteQuery();/pstm.ExecuteReader().

5、封装结果集:While(result.next()){......}

6、释放资源:Result.close();  Pstm.close();  Connection.close();

分析:如果我们要在mysql中查询一个内容应该怎样操作才能得到结果?在mysql中我们只需要编写出相应的sql语句即可,但是在jdbc中除了编写sql语句外还要做很多额外的工作才能得到结果。能不能只编写sql语句其余工作不再去关注呢?这就是mybatis的作用,我们唯一要做的就是思考怎样去编写高效的sql语句。

4 Mybatisの入门案例

创建maven控制台程序,按照以下步骤完成mybatis开发环境搭建工作:

  • pom.xml 导入mybatis依赖
<!-- mybatis依赖-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.6</version>
</dependency>
<!-- 数据库驱动-->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.0.33</version>
</dependency>
  • 项目中引入数据库配置文件:db.properties
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/bookstore?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&useSSL=false
jdbc.username=root
jdbc.password=root
  • 添加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>
<!--
 加载数据库配置信息的文件:db.properties
 properties:读取classpath下面的配置文件,所以不用写classpath
 -->
  <properties resource="db.properties"/>
<!--
配置数据库连接信息:从配置文件读取对应的key的值
-->
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
      </dataSource>
    </environment>
  </environments>
</configuration>

mybatis-confg.xml是mybatis的核心配置文件,该文件中要配置哪些东西呢:即基本的sql语句执行环境需要的就是数据驱动类型、数据库服务器地址、数据库服务器登录账号及密码。

  • 测试代码
  public class Tester {
      @Test
      public void test01()throws  Exception{
          //1.读取配置文件,构建mybatis核心执行对象
          String resource = "mybatis-config.xml";
          InputStream inputStream = Resources.getResourceAsStream(resource);
          //SqlSession可以理解为Connection,此行代码的意思就是:读取配置文件获取数据库连接的工厂对象
          SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
          //通过工厂获取数据库连接对象
          SqlSession session = sqlSessionFactory.openSession();
          System.out.println(session);
      }
  }

5 Mybatis的CRUD操作

准备工作

确定要操作的数据库和数据表后,按照持久层dao开发流程,完成代码准备:

创建Notice实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Notice {
    private Integer id;
    private String title;
    private String content;
    private Integer employeeId;
    //jdk1.8新增日期格式:LocalDateTime对象中包含:年月日时分秒组成
    private LocalDateTime createTime;
}

为表创建对应的dao接口:NoticeDao.java

/**
 * mybatis接口不再提供实现类,取而代之使用xml配置文件完成代码执行,
 * xml文件主要作用:写sql,成为sql映射文件
 * sql映射文件要求:文件名必须和接口同名,单词一模一样;文件存放位置和接口所在包一样
 */
public interface NoticeDao {
    /**
     * 查询所有的帖子信息
     */
    List<Notice> selectAll();
}

为NoticeDao接口创建SQL映射文件:NoticeDao.xml

mybatis中规定dao只是用来声明方法的接口类,接口中方法的实现由mapper.xml映射文wrh来实现(sql语句)。

<?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:sql映射文件.作用:配置sql语句
  namespace:命名空间,理解包 设置当前sql映射文件是对应哪个接口文件,填写接口包名.类名
-->
<mapper namespace="com.woniu.dao.NoticeDao">
    <select id="" resultType="">
        
    </select>
</mapper>

注意:mapper.xml映射文件须和dao接口在同一包中且名称相同。创建好的文件目录如下图所示:

案例一:查询所有公告信息

修改NoticeDao.xml映射文件:为查询方法配置对应的sql语句

注意:使用mapper代理开发时不用为dao接口编写实现类,但是在mapper.xml文件中的mapper节点中的namespace必须指定与同名接口类的全限定名

<!--
id:指定sql为接口里面哪个方法提供的。id设置方法:方法名
resultType:方法返回值类型,如果方法返回值类型是集合,只需要填写集合泛型类型即可
 指定类型时:必须使用全路径:包名.类名
-->
<select id="selectAll" resultType="com.woniu.entity.Notice">
    SELECT id,title,content,employeeId,createtime from wy_notice
</select>

属性说明:

 id: 设置要实现的方法名称
 resultType: 方法返回值类型,如果返回值是集合类型,写集合的泛型类型。
 鉴于之前使用JdbcTemplate执行sql的经验,我们可以这么理解resultType:
  resultType提供类型用于完成查询结果与实体类映射关系,默认情况:entity定义属性时属性名和表列名单词一样的。

注意:使用mapper代理开发时mapper.xml中sql节点的id值必须与dao接口中的方法名称一致

		resultType属性的值必须与dao接口中方法的返回值类型一致

核心配置文件中注册Sql映射文件:NoticeDao.xml

 每一个mapper.xml映射文件要能正确被程序解析到,还要在mybatis核心配置文件中进行注册

<mappers>
    <mapper resource="com/woniu/dao/NoticeDao.xml"/>
</mappers>

		resource读取的是classpath下的文件url地址,所以此处写的是xml的路径,不是接口的包名

常见异常:

如果没有在核心配置文件中"注册"sql映射文件,执行代码时通常会提示以下异常:

测试代码

@Test
public void test01()throws  Exception{
    //1.读取配置文件,构建mybatis核心执行对象
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    //SqlSession可以理解为Connection,此行代码的意思就是:读取配置文件获取数据库连接的工厂对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //通过工厂获取数据库连接对象
    SqlSession session = sqlSessionFactory.openSession();
    System.out.println(session);
    //执行sql语句了 重点,mapper其实就是依靠jdk代理为接口生成实现类
    NoticeDao noticeDaoImpl = sqlSession.getMapper(NoticeDao.class);
    //com.sun.proxy.$Proxy6 基于jdk代理,获取接口对应的实现类
    System.out.println(noticeDaoImpl.getClass());

    List<Notice> notices = noticeDaoImpl.selectAll();
    notices.forEach(System.out::println);
}

观察以上输出结果,我们不难发现,mybatis的底层其实是利用JDK代理完成了dao接口对应实现类的动态生成。我们之前学过JDK代理的知识,我们可以这么理解

所以,在mybatis 框架中它将"通用且重复"的sql执行过程在程序运行过程中,动态增强到xml配置的sql语句前后。简化了程序员持久层开发工作的负担和繁复。

案例二:DML操作の添加公告信息

修改NoticeDao.xml映射文件:为insert方法配置对应的sql语句

注意:使用mapper代理开发时不用为dao接口编写实现类,但是在mapper.xml文件中的mapper节点中的namespace必须指定与同名接口类的全限定名

<insert id="insert">
    insert wy_notice values(null,'测试数据','测试数据内容',1,now())
</insert>

属性说明:

 id: 设置要实现的方法名称
 insert配置新增语句,该标签一般只需要配置id即可
 因为insert没有查询结果集,所以insert标签也就没有resultType属性了

测试代码

@Test
public void testInsert()throws IOException{
    //1.获取SqlSession
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession(true);
    //2.基于jdk代理,获取要测试接口对应的实现类
    NoticeDao noticeDaoImpl = sqlSession.getMapper(NoticeDao.class);
    int insert = noticeDaoImpl.insert(null);
    System.out.println("insert语句执行后,受影响的行数:" + insert);
}

添加代码执行成功后,数据库数据并没有新增的问题:

测试结果暗示的意思是数据添加成功,我们去刷新mysql数据库,观察发现并没有出现"测试数据"这条新增数据,why???

真相是:mybatis在openSession()时,默认开启了事务手动提交模式,所以在没有代码明确写明"commit()"的情况下,程序操作的结果不会物理更新到数据表中。

解决方案如下:

方案一:手动提交事务

@Test
public void test01()throws  Exception{
    //1.读取配置文件,构建mybatis核心执行对象
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    //SqlSession可以理解为Connection,此行代码的意思就是:读取配置文件获取数据库连接的工厂对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //通过工厂获取数据库连接对象,SqlSession默认配置:事务手动提交,如果openSession(true)设置事务自动提交
    SqlSession session = sqlSessionFactory.openSession();       
    //sql执行:获取UserDao接口的代理对象,
    NoticeDao noticeDaoImpl = session.getMapper(NoticeDao.class);

    int i = noticeDaoImpl.insert(null);
    System.out.println("新增方法执行结果是:" + i);
    //提交事务
    session.commit();
    //释放资源
    session.close();
}

方案二:开启事务自动提交模式

@Test
public void test01()throws  Exception{
    //1.读取配置文件,构建mybatis核心执行对象
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    //SqlSession可以理解为Connection,此行代码的意思就是:读取配置文件获取数据库连接的工厂对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //通过工厂获取数据库连接对象,SqlSession默认配置:事务手动提交,如果openSession(true)设置事务自动提交
    SqlSession session = sqlSessionFactory.openSession(true);       
    //sql执行:获取UserDao接口的代理对象,
    NoticeDao noticeDaoImpl = session.getMapper(NoticeDao.class);

    int i = noticeDaoImpl.insert(null);
    System.out.println("新增方法执行结果是:" + i);
   
    //释放资源
    session.close();
}

扩展补充:SqlSession释放资源的问题

SqlSession使用完毕后,也是要释放数据库资源的,所以此处如果想不写"session.close()",可以借助JDK1.8中try-catch的新语法,代码如下所示:

@Test
    public void test01()throws  Exception{
        //1.读取配置文件,构建mybatis核心执行对象
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //SqlSession可以理解为Connection,此行代码的意思就是:读取配置文件获取数据库连接的工厂对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //通过工厂获取数据库连接对象,SqlSession默认配置:事务手动提交,如果openSession(true)设置事务自动提交
        try(SqlSession session = sqlSessionFactory.openSession(true);) {
            //sql执行:获取UserDao接口的代理对象,
            NoticeDao noticeDaoImpl = session.getMapper(NoticeDao.class);
           
            int i = noticeDaoImpl.insert(null);
            System.out.println("新增方法执行结果是:" + i);
        }
    }

6 日志框架 logback

模仿日志输出,日志将程序执行过程,执行了什么sql,带入什么参数,执行的是什么结果,执行出现问题具体描述信息...

作用:日志框架增强程序员跟踪程序执行过程,对于发生一些程序问题,更好进行定位、分析。

日志框架选择:Springboot内置日志框架:logback

logback使用步骤

  • pom.xml导入logback的依赖:整个日志框架需要导入三个依赖:logback-classic、logback-core、slf4j-api

  <!-- 日志框架 -->
  <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.2.3</version>
  </dependency>
  <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-core</artifactId>
      <version>1.2.3</version>
  </dependency>
  <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.25</version>
  </dependency>
  • 项目引入自己的日志配置文件:logback.xml

  <configuration debug="false">
      <!-- appender配置输出的位置和输出日志格式
         CONSOLE:控制台
         -->
      <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
          <encoder>
              <pattern>%5p | %-40.40logger{39}  :%m%n</pattern>
              <charset>utf8</charset>
          </encoder>
      </appender>
      <!-- 了解日志信息输出的级别:TRACE < DEBUG < INFO < WARN < ERROR
          配置哪些日志在控制台输出,可以通过设置不同的日志级别控制
          日志级别的输出原则:设置级别时,会输出当前设置级别的日志以及比当前更高级别的日志
         -->

  <!--
  logger:特定设置,属性
  name:特定配置针对的包名,酌情修改
  level:DEBUG
  additivity:覆盖默认配置
  -->
      <logger name="com.woniu" level="DEBUG" additivity="false">
          <appender-ref ref="CONSOLE"/>
      </logger>
  <!--
  root:基础配置,level:info
  -->
      <root level="INFO">
          <appender-ref ref="CONSOLE" />
      </root>
  </configuration>
  • 自己使用log对象输出日志信息

  @Slf4j //引入日志管理器
  public class xxx{
     @Test
      public void test00(){
          String str="helloworld";
          //观察控制台能否输出对应的信息:信息是否可以输出与logback.xml配置的级别有关系
          log.trace("trace str{}",str);
          log.debug("debug str{}",str);
          log.info("info str{}",str);
          log.warn("warn str{}",str);
          log.error("error str{}",str);
      }
  }

按照步骤配置好日志框架后,重新启动上面案例的测试代码,就可以在控制台看到sql的输出信息了。

7 mybatis中#{}和${}设置sql参数对比

案例:根据id查询用户信息

修改UserDao.xml中配置:使用${}带入参数

 <select id="selectById" resultType="com.woniu.entity.Users">
    select * from wy_employee where id=${id}
  </select>

执行测试方法

@Test
public void test01()throws  Exception{
    //1.读取配置文件,构建mybatis核心执行对象
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    //SqlSession可以理解为Connection,此行代码的意思就是:读取配置文件获取数据库连接的工厂对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //通过工厂获取数据库连接对象,SqlSession默认配置:事务手动提交,如果openSession(true)设置事务自动提交
    try(SqlSession session = sqlSessionFactory.openSession(true);) {
        System.out.println(session);
        //sql执行:获取UserDao接口的代理对象,
        UsersDao usersDaoImpl = session.getMapper(UsersDao.class);
        System.out.println(usersDaoImpl.getClass());
        //调用方法,获取执行结果
        List<Users> list = usersDaoImpl.selectAll("id");
        list.forEach(System.out::println);
    }
}

修改UserDao.xml中配置:使用#{}带入参数

 <select id="selectById" resultType="com.woniu.entity.Users">
    select * from wy_employee where id=#{id}
  </select>

再次执行同一个测试方法,观察两次测试的日志输出结果:

由上图得出以下结论:

#{}与${}的区别:
   #{}:使用?占位符,即是将sql语句编译好后再取值,能够有效防止sql注入,#{}取的是属性中的值
   ${}:是sql后面直接拼接参数值,即取值后再编译语句,不能防止注入。
适用场景:${}方式一般用于传入数据库对象,例如传入表名或者列名,就只能使用${}带入参数。
       在实际使用中能使用#{}就不用${},

案例2:根据指定列名完成查询结果的排序

  • UsersDao.java中定义方法

      List<Users> selectAll(String name);
    
  • UsersDao.xml配置sql

    select * from wy_employee order by ${name} desc

  • 测试代码

@Test
public void test01()throws  Exception{
    //1.读取配置文件,构建mybatis核心执行对象
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    //SqlSession可以理解为Connection,此行代码的意思就是:读取配置文件获取数据库连接的工厂对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //通过工厂获取数据库连接对象,SqlSession默认配置:事务手动提交,如果openSession(true)设置事务自动提交
    try(SqlSession session = sqlSessionFactory.openSession(true);) {
        System.out.println(session);
        //sql执行:获取UserDao接口的代理对象,
        UsersDao usersDaoImpl = session.getMapper(UsersDao.class);
        System.out.println(usersDaoImpl.getClass());
        //调用方法,获取执行结果
        List<Users> list = usersDaoImpl.selectAll("id");
        list.forEach(System.out::println);
    }
}
  • 将UserDao.xml中sql修改成#{}再次执行单元测试
<select id="selectAll" resultType="com.woniu.entity.Users">
    select * from wy_employee order by #{name} desc
</select>

观察执行结果可以发现:#{}带入参数时,查询结果没有排序。只有使用${}带入参数时,才有排序的效果

8 #{}带入参数的写法

#{}带入参数时,使用什么名称来引用值,分为三种场景区别:
1 方法有且只有一个参数,并参数是基本类型或String,#{随便写}
形如:Users selectById(Long id); 
配置sql:select * from wy_employee where id=#{随便写,一般见词知意形参名}

2 方法有且只有一个参数,并参数是对象,#{对象的属性名}
形如:insert(user  u) update(User u)

3 方法N个参数 #{参数} 借助注解@Param给参数设置引用名
形如:
int update(@Param("pkId") Long id, @Param("pwd") String password,@Param("username") String realName);
xml文件:update wy_employee set password=#{pwd},realname=#{username} where id=#{pkId}

案例:新增公告信息

  • NoticeDao.java定义新增方法
  int insert(Notice notice);
  • NoticeDao.xml配置insert语句
  <insert id="insert">
      insert wy_notice values(null,#{title},#{content},#{userId},#{createTime})
  </insert>
  • MyTester.java测试代码
  @Test
  public void test02()throws Exception{
      //1 指定mybatis开发环境基于哪个配置文件来使用resource基于classpath
      String resource = "mybatis-config.xml";
      //2 基于配置文件构建IO输入流
      InputStream inputStream = Resources.getResourceAsStream(resource);
      //SqlSession本质就是Connection   SqlSessionFactory是连接池
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
      //执行sql语句,验证执行结果,连接池获取连接对象时,其实需要指定事务提交方式,如果不指定默认不是自动提交模式
      try (SqlSession session = sqlSessionFactory.openSession(true)) {
          //获取dao实现类对象,mybatis依据JDK代理模式动态获取实现类型
          NoticeDao implProxy = session.getMapper(NoticeDao.class);
          int i = implProxy.insert(
              Notice.builder()
              .title("aaaaaaaaaaaa")
              .content("bbbbbbbbbbbbbbbbbbbbbbb")
              .userId(1)
              .createTime(LocalDateTime.now())
              .build()
          );
          System.out.println("本次新增的数据行数:"+i);
      }
  • 跟踪控制台输出的日志结果

常见坑点

sql映射文件没有在核心配置文件中注册时:

Sql映射文件和所实现的接口文件不在同一个目录

如何确认接口文件和sql映射文件是否在一个目录呢?可以通过mvn:compile编译项目后,观察target中的classes目录。同一目录的现象如下所示:

UsersDao.java和UsersDao.xml"紧紧挨在一起"

i

相关推荐
职略1 小时前
负载均衡类型和算法解析
java·运维·分布式·算法·负载均衡
A22741 小时前
LeetCode 196, 73, 105
java·算法·leetcode
容若只如初见2 小时前
项目实战--Spring Boot + Minio文件切片上传下载
java·spring boot·后端
阿里巴巴P8资深技术专家3 小时前
Java常用算法&集合扩容机制分析
java·数据结构·算法
weixin_440401693 小时前
分布式锁——基于Redis分布式锁
java·数据库·spring boot·redis·分布式
码农爱java3 小时前
Spring Boot 中的监视器是什么?有什么作用?
java·spring boot·后端·面试·monitor·监视器
zengson_g3 小时前
当需要对大量数据进行排序操作时,怎样优化内存使用和性能?
java·数据库·算法·排序算法
血战灬狂龙4 小时前
pom.xml文件加载后没有变成maven图标
xml·java·maven
无名指的等待7124 小时前
SpringBoot实现图片添加水印(完整)
java·spring boot·后端
胡尚4 小时前
Ratf协议图解、Nacos CP集群源码分析
java·spring boot