MyBatis(一)

简介

官网:https://mybatis.org/mybatis-3/zh_CN/index.html

Github:https://github.com/mybatis

什么是MyBatis

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

持久化

持久化指的是数据在程序终止后依然存在的能力。持久化数据存储有许多种形式,这取决于你需要的数据的存储级别和持久性。这包括:

  1. 数据库:用于存储大量数据,并提供查询和更新等功能。常见的数据库有关系型数据库(如MySQL,Oracle),面向文档的数据库(如MongoDB),键值对数据库(如Redis),图数据库(如Neo4j)等。
  2. 文件系统:如果你只需要存储简单的数据,像是字符串,数字,或列表,你可以选择将这些数据存储到文件中。
  3. 云存储:数据可以存储在远程的服务器上,这通常通过云服务提供商实现,比如Amazon S3, Google Cloud Storage等。

持久层

​ 在软件架构中,持久层是关于数据存储和检索的部分,它也被称为数据库层。数据持久化意味着数据可以在程序的生命周期之外存在,一般通过存储在数据库或者文件系统中来实现。在一个典型的三层架构中,持久层位于业务逻辑层和表示层之下,主要负责如下任务:

  1. 与数据库的交互,如增删查改(CRUD)操作。
  2. 数据库连接的管理,包括打开和关闭连接。
  3. 在需要的时候提供事务管理。

​ 一般来说,持久层中的代码不应包含任何业务逻辑,它只关心如何对数据进行操作。这样做的目的是为了保持业务逻辑的清晰和一致,同时也便于将持久层进行替换或升级,比如从关系型数据库切换至NoSQL数据库。

​ 在Java中,常见的持久层框架有Hibernate、MyBatis等,在Python中,则常见的有SQLAlchemy、Django ORM等。

从 XML 中构建 SqlSessionFactory

  1. XML文件

    <configuration>
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"/>
                    <property name="username" value="root"/>
                    <property name="password" value="59421wjh"/>
                </dataSource>
            </environment>
        </environments>
        <mappers>
            <mapper resource="org/mybatis/example/BlogMapper.xml"/>
        </mappers>
    </configuration>
    
  2. MyBatisUtils工具类

    public class MyBatisUtils {
        private static SqlSessionFactory sqlSessionFactory;
        static {
            try {
                String resource = "org/mybatis/example/mybatis-config.xml";
                InputStream inputStream = Resources.getResourceAsStream(resource);
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    //    有了SqlSessionFactory,就可以从中获取SqlSession实例了
    //    SqlSession完全包含了面向数据库执行sql命令的所有方法
        public static SqlSession getSqlSession(){
            return sqlSessionFactory.openSession();
        }
    }
    
  3. 编写接口和方法

    List<User> getUserList();
    
  4. 编写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">
    
    <mapper namespace="org.example.dao.UserDao">
        <select id="getUserList" resultType="org.example.pojo.User">
            select * from user
        </select>
    </mapper>
    
    • namespace

      命名空间的作用有两个,一个是利用更长的全限定名来将不同的语句隔离开来,同时也实现了你上面见到的接口绑定。就算你觉得暂时用不到接口绑定,你也应该遵循这里的规定,以防哪天你改变了主意。 长远来看,只要将命名空间置于合适的 Java 包命名空间之中,你的代码会变得更加整洁,也有利于你更方便地使用 MyBatis。

      命名解析: 为了减少输入量,MyBatis 对所有具有名称的配置元素(包括语句,结果映射,缓存等)使用了如下的命名解析规则。

      • 全限定名(比如 “com.mypackage.MyMapper.selectAllThings)将被直接用于查找及使用。
      • 短名称(比如 “selectAllThings”)如果全局唯一也可以作为一个单独的引用。 如果不唯一,有两个或两个以上的相同名称(比如 “com.foo.selectAllThings” 和 “com.bar.selectAllThings”),那么使用时就会产生“短名称不唯一”的错误,这种情况下就必须使用全限定名。
    • xml配置中的id必须和接口中对应的方法名称相同

    • resultType必须以全限定名来写

  5. 测试

    @Test
        public void test(){
    //      获得sqlSession对象
            SqlSession sqlSession = MyBatisUtils.getSqlSession();
    //        方式一:getMapper
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            List<User> userList = mapper.getUserList();
            for (User user : userList) {
                System.out.println(user);
            }
            sqlSession.close();
        }
    

作用域和生命周期

在这里插入图片描述

SqlSessionFactoryBuilder

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

SqlSessionFactory

  • 可以当作数据库连接池

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

  • 最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。

  • 因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

SqlSession

  • 连接池的一个请求
  • 开启和关闭,无法共享,放在方法中
  • 用完之后赶紧关闭,否则资源被占用

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:

try (SqlSession session = sqlSessionFactory.openSession()) {
  // 你的应用逻辑代码
}

在所有代码中都遵循这种使用模式,可以保证所有数据库资源都能被正确地关闭。
在这里插入图片描述

这里的每个Mapper都代表一个具体的业务!

增删改查

  1. 增删改必须提交事务

    sqlSession.commit();
    
  2. 万能的Map

    • 接口

      int addUser2(Map<String, Object> map);
      
    • xml文件

      <insert id="addUser2" parameterType="map">
          insert into user(id,name,pwd) values (#{userID},#{userName},#{userPassword})
      </insert>
      
    • 测试方法

      @Test
          public void test3(){
      //      获得sqlSession对象
              SqlSession sqlSession = MyBatisUtils.getSqlSession();
      //        方式一:getMapper
              UserMapper mapper = sqlSession.getMapper(UserMapper.class);
              Map<String,Object> map = new HashMap<>();
              map.put("userID",7);
              map.put("userName","hello");
              map.put("userPassword","123456");
              mapper.addUser2(map);
              sqlSession.commit();
              sqlSession.close();
          }
      

#{} 和 ${}

  1. #{}:这是参数占位符。MyBatis会将这种形式的参数安全地设置到预编译的(prepared)SQL语句中,这种方式可以防止SQL注入攻击。举个例子SELECT * FROM table WHERE id = #{id},在执行查询之前,MyBatis会用参数id的值替换掉#{id},并确保这个值不会被当作SQL命令执行。
  2. ${}:这是字符串替换工具。MyBatis会直接把${}中的值替换到SQL语句中。如果参数是一个用户输入的值,它会增加SQL注入的风险。因此,使用${}的情况应避免让用户直接输入值。举个例子 SELECT * FROM ${tableName},MyBatis会使用参数tableName的值直接替换掉${tableName}。如果tableName参数是用户输入的,那就存在SQL注入的风险。

@Param是一个用于参数绑定的注解,常用于MyBatis和Spring框架等中。它用于将方法参数传递给映射语句并将它们绑定到SQL的参数中。

在下面的示例中,我们将使用@Param来填充SQL查询的参数:

public interface BookMapper {
    @Select("SELECT * FROM book WHERE title = #{name}")
    Book findByName(@Param("name") String name);
}

在这个例子中,@Param("name")注解表明findByName方法的name参数应作为SQL查询字符串中的值(#{name})传入。这就使得MyBatis知道当执行该查询时,它应该将title的值设置为你传给findByName方法的参数。

注意,对于简单的情形,比如只有一个参数的方法,你也可以不使用@Param注解。但是对于多参数的方法,使用@Param可以提供更清晰、明确的代码,让读者更容易理解参数的用途。

配置

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:

ResultMap

resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的数千行代码。

  • 简单地将所有的列映射到 HashMap 的键上

  • MyBatis 会在幕后自动创建一个 ResultMap,再根据属性名来映射列到 JavaBean 的属性上。如果列名和属性名不能匹配上,可以在 SELECT 语句中设置列别名(这是一个基本的 SQL 特性)来完成匹配。

    <select id="selectUsers" resultType="User">
      select
        user_id             as "id",
        user_name           as "userName",
        hashed_password     as "hashedPassword"
      from some_table
      where id = #{id}
    </select>
    

Log4j

  1. 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/free.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
    
  2. pom.xml配置

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

    但是这个版本存在问题,仅用于学习

  3. mybatis-config.xml

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

MyBatis执行流程

在这里插入图片描述

多对一和一对多

  1. 实体类

    public class Teacher {
        private int id;
        private String name;
        private List<Student> student;
    }
    
    public class Student {
        private int id;
        private String name;
        private int tid;
        private Teacher teacher;
    }
    
  2. 获取老师信息:一对多的情况

    • 接口
    Teacher getTeacher2(@Param("tid") int id);
    /*返回结果
        Teacher(
            id=1, name=秦老师,
                student=[
                Student(id=1, name=小明, tid=0, teacher=null),
                Student(id=2, name=小红, tid=0, teacher=null),
                Student(id=3, name=小张, tid=0, teacher=null),
                Student(id=4, name=小李, tid=0, teacher=null),
                Student(id=5, name=小王, tid=0, teacher=null)
                ]
        )
        */
    Teacher getTeacher3(@Param("tid")int id);
    
    • xml文件
    <!--按照结果嵌套查询-->
        <select id="getTeacher2" resultMap="teacherStudent">
            select s.id sid,s.name sname,t.name tname,t.id tid
            from student s,teacher t
            where s.tid = t.id and t.id = #{tid}
        </select>
    
        <resultMap id="teacherStudent" type="teacher">
            <result property="id" column="tid"/>
            <result property="name" column="tname"/>
    
    <!--复杂的属性,我们需要单独处理
    对象使用association javaType 指定属性的类型
    集合使用collection 集合中的泛型信息,我们用ofType获取-->
            <collection property="student" ofType="student">
                <result property="id" column="sid"/>
                <result property="name" column="sname"/>
            </collection>
        </resultMap>
    
    <!--  =========================================================  -->
    <!--按照查询嵌套处理-->
    
    <select id="getTeacher3" resultMap="teacherStudent3">
            select * from teacher where id = #{tid}
        </select>
        <resultMap id="teacherStudent3" type="teacher">
            <collection property="student" column="id" javaType="ArrayList" ofType="student" select="getStudentByTeacherId" />
        </resultMap>
        <select id="getStudentByTeacherId" resultType="student">
            select * from student where tid = #{tid}
        </select>
    
  3. 获取学生信息:包括老师实体(多对一)

    • 接口
    List<Student> getStudent2();
    List<Student> getStudent3();
    
    • xml文件
    <!--按照结果嵌套查询-->
           <select id="getStudent2" resultMap="StudentTeacher2">
              select s.id sid,s.name sname,t.name tname
              from student s,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>
    
    <!--  =========================================================  -->
    <!--按照查询嵌套处理-->
        <select id="getStudent3" resultMap="StudentTeacher3">
            select * from student
        </select>
        <resultMap id="StudentTeacher3" type="student">
            <result property="id" column="id"/>
            <result property="name" column="name"/>
            <association property="teacher" column="tid" javaType="teacher" select="getTeacher"/>
        </resultMap>
    
        <select id="getTeacher" resultType="teacher">
            select * from teacher
        </select>
    
  • 12
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值