MyBatis初学者必看:快速上手,让你的项目事半功倍!

news2024/11/28 17:52:31

为什么使用MyBatis

在Java程序中去连接数据库,最原始的办法是使用JDBC的API。我们先来回顾一下使用JDBC的方式,我们是如何操作数据库的。

Connection conn = null;
Statement stmt = null;
Blog blog = new Blog();

try {
   // 注册 JDBC 驱动
   Class.forName("com.mysql.jdbc.Driver");
   // 打开连接
   conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis", "root", "123456");
   // 执行查询
   stmt = conn.createStatement();
   String sql = "SELECT bid, name, author_id FROM blog";
   ResultSet rs = stmt.executeQuery(sql);
   // 获取结果集
   while (rs.next()) {
      Integer bid = rs.getInt("bid");
      String name = rs.getString("name");
      Integer authorId = rs.getInt("author_id");
      blog.setAuthorId(authorId);
      blog.setBid(bid);
      blog.setName(name);
    }
    System.out.println(blog);
    rs.close();
        stmt.close();
        conn.close();
    } catch (SQLException se) {
        se.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if (stmt != null) stmt.close();
        } catch (SQLException se2) {
        }
        try {
            if (conn != null) conn.close();
        } catch (SQLException se) {
            se.printStackTrace();
        }
    }
}

首先,我们在pom.xml中添加MySQL驱动的依赖。
第一步,注册驱动
第二步,通过DriverManager获取一个connection,参数中是数据库连接地址,用户名和密码
第三步,通过connection创建一个Statement对象
第四步,通过statement的execute方法执行SQL,返回结果集。当然Statement中提供了很多的方法。
第五步,通过ResultSet获取数据,转换成一个POJO对象
最后,我们要关闭数据库的相关资源,包括ResultSet、Statement、Connection,他们的关闭顺序和打开的顺序正好相反

上面就是我们用过JDBC的API去操作数据库的方法,但是这仅仅是一个非常简单的查询的方法。如果我们的项目中的业务比较复杂,表也比较多,各种对数据库的增删改查的方法也比较多的话,这样的代码会重复很多次,维护起来也非常的痛苦。主要体现在下面几点:

  1. 在每一段这样的代码里面,我们都要自己去管理数据库的连接资源,如果忘记close,就可能造成数据库服务连接耗尽。
  2. 处理业务逻辑和处理数据的代码耦合在一起。如果业务流程复杂,和数据库交互次数较多,耦合在代码中的SQL就会非常多。如果要修改业务逻辑,或者修改数据库环境(因为不同数据库的语法可能不同),这个会非常麻烦,工作量也很难去估计。
  3. 处理结果集的时候,我们要把ResultSet转化成POJO对象,必须根据字段属性的类型一个一个的去处理,写这样的代码非常的枯燥。

正因为上述几点原因,我们在实际工作中是很少直接使用JDBC的,那在Java程序中有哪些更加简单的操作数据库的方式呢?
1、 Apache DBUtils: 官网.
DBUtils解决的最核心的问题就是结果集的映射,可以把ResultSet封装成JavaBean,它是怎么实现的那?
首先,DBUtils提供了一个QueryRunner类,它对数据库的增删改查的方法进行了封装,那么我们操作的数据库就可以直接使用它提供的方法。
在QueryRunner的构造方法中,我们又可以传入一个数据源,这样我们就不需要再去写各种创建和释放连接的代码了。

queryRunner = new QueryRunner(dataSource);

那么怎么把结果集转换成对象呢?比如实体类Bean或者List或者Map? 在DBUtils里面提供了一系列的支持泛型的ResultSetHandler。
在这里插入图片描述
我们只需要在DAO层调用QueryRunner的查询方法,传入这个handler,它就可以自动把结果集转换成实体Bean

 String sql = "select * from blog where bid = ? ";
 Object[] params = new Object[]{bid};
 BlogDto blogDto = queryRunner.query(sql, new BeanHandler<>(BlogDto.class), params);

没用过DBUtils的同学可以思考一下,通过结果集到实体类的映射是怎么实现的?
也就是说,我只传了一个实体类的类型,它怎么知道这个类型有哪些属性,每个属性是什么类型?然后创建这个对象并给这些字段赋值的?答案是通过反射实现。
需要注意的是,DBUtils中要求数据库字段和对象的属性名完全一致,才可以实现自动转化。

2、 Spring JDBC
除了DBUtils之外,Spring也对原生的JDBC做了封装,并且提供了一个模板方法JdbcTemplate,来简化我们对数据库的操作。
首先,我们不需要关心资源管理的问题。其次,对于结果集的处理,Spring JDBC提供了一个RowMapper接口,可以把结果集转换成Java对象。
看下面代码: 比如我们要把结果集转换成Employee对象,就可以针对Employee创建一个RowMapper对象,实现RowMapper接口,并且重写mapRow方法。我们在mapRow中完成对结果集到Java对象的转换。

public class EmployeeRowMapper implements RowMapper {
    @Override
    public Object mapRow(ResultSet resultSet, int i) throws SQLException {
        Employee employee = new Employee();
        employee.setEmpId(resultSet.getInt("emp_id"));
        employee.setEmpName(resultSet.getString("emp_name"));
        employee.setGender(resultSet.getString("gender"));
        employee.setEmail(resultSet.getString("email"));

        return employee;
    }
}

在DAO层调用的时候就可以传入自定义的RowMapper类,最终返回我们所需要的类型,结果集和实体类型的映射也是自动完成的。

public List<Employee> query(String sql) { 
	new JdbcTemplate( new DruidDataSource()); 
	return jdbcTemplate.query(sql,new EmployeeRowMapper()); 
}

通过这种方式,我们对结果集的处理只需要写一次代码,然后在每个需要映射的地方传入这个RowMapper就可以了,可以减少很多重复的代码。

但是这种方式还是有个问题:每一个实体类都要有对应的RowMapper,然后要对每一个RowMapper编写getInt、getString这样的代码,还增加了类的数量。
那有没有办法实现结果集和实体类的字段自动映射呢?这样,我们要解决两个问题,一个是名称的对应问题,另外一个是字段类型对应的的问题。
我们可以创建一个BaseRowMapper类,通过反射的方式自动获取所有属性,把表字段全部复制到属性。

public class BaseRowMapper<T> implements RowMapper<T> {

    private Class<?> targetClazz;
    private HashMap<String, Field> fieldMap;

    public BaseRowMapper(Class<?> targetClazz) {
        this.targetClazz = targetClazz;
        fieldMap = new HashMap<>();
        Field[] fields = targetClazz.getDeclaredFields();
        for (Field field : fields) {
            fieldMap.put(field.getName(), field);
        }
    }

    @Override
    public T mapRow(ResultSet rs, int arg1) throws SQLException {
        T obj = null;

        try {
            obj = (T) targetClazz.newInstance();

            final ResultSetMetaData metaData = rs.getMetaData();
            int columnLength = metaData.getColumnCount();
            String columnName = null;

            for (int i = 1; i <= columnLength; i++) {
                columnName = metaData.getColumnName(i);
                Class fieldClazz = fieldMap.get(camel(columnName)).getType();
                Field field = fieldMap.get(camel(columnName));
                field.setAccessible(true);

                // fieldClazz == Character.class || fieldClazz == char.class
                if (fieldClazz == int.class || fieldClazz == Integer.class) { // int
                    field.set(obj, rs.getInt(columnName));
                } else if (fieldClazz == boolean.class || fieldClazz == Boolean.class) { // boolean
                    field.set(obj, rs.getBoolean(columnName));
                } else if (fieldClazz == String.class) { // string
                    field.set(obj, rs.getString(columnName));
                } else if (fieldClazz == float.class) { // float
                    field.set(obj, rs.getFloat(columnName));
                } else if (fieldClazz == double.class || fieldClazz == Double.class) { // double
                    field.set(obj, rs.getDouble(columnName));
                } else if (fieldClazz == BigDecimal.class) { // bigdecimal
                    field.set(obj, rs.getBigDecimal(columnName));
                } else if (fieldClazz == short.class || fieldClazz == Short.class) { // short
                    field.set(obj, rs.getShort(columnName));
                } else if (fieldClazz == Date.class) { // date
                    field.set(obj, rs.getDate(columnName));
                } else if (fieldClazz == Timestamp.class) { // timestamp
                    field.set(obj, rs.getTimestamp(columnName));
                } else if (fieldClazz == Long.class || fieldClazz == long.class) { // long
                    field.set(obj, rs.getLong(columnName));
                }

                field.setAccessible(false);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return obj;
    }

    /**
     * 下划线转驼峰
     * @param str
     * @return
     */
    public static String camel(String str) {
        Pattern pattern = Pattern.compile("_(\\w)");
        Matcher matcher = pattern.matcher(str);
        StringBuffer sb = new StringBuffer(str);
        if(matcher.find()) {
            sb = new StringBuffer();
            matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
            matcher.appendTail(sb);
        }else {
            return sb.toString();
        }
        return camel(sb.toString());
    }
}

这样的话,调用的地方就可以改成下面这样:

return jdbcTemplate.query(sql,new BaseRowMapper(Employee.class));

这样 ,我们在使用的时候只要传入需转换类型就可以了不再 单独 创建 一个 RowMapperRowMapperRowMapperRowMapper RowMapper 。

总结起来,DBUtils和Spring JDBC这两个对JDBC做了轻量封装的框架,可以帮助我们解决下面这些问题:

  1. 无论是QueryRunner还是JdbcTemplate,都可以传入一个datasource进行初始化,也就是资源管理这部分可以交给专门的数据源组件去管理,不用我们手动的去创建和关闭。
  2. 对数据库的增删改查都做了封装
  3. 可以帮助我们映射结果集,无论是映射成List、Map还是实体类

但是这两个框架还是有一些不足:

  1. SQL语句都是写在代码中的,硬编码问题没有解决
  2. 参数只能按照固定位置的顺序传入,通过占位符去替换,不能自动映射
  3. 在方法里面,可以把结果集映射成实体类,但是没有办法把实体类映射成数据库可执行的SQL
  4. 查询没有缓存功能

3、 Hibernate
要解决上面的问题,使用这些工具类是不够的,我们要使用ORM框架来帮我们解决程序对象和关系型数据库的相互映射的问题。
Hibernate是一个很流行的ORM框架,在使用Hibernate的时候,我们需要为实体类创建一些hbm的xml映射文件,或者是用类似于@Table这样的注解。例如:

 <hibernate-mapping>
 	<class name="com.yrk.User" table="User">
 		<id name="id">
 			<generator class="native"/>
 		</id>
 		<property name="password"/>
 		<property name="userName"/>
 	</class>
 </hibernate-mapping>

然后通过Hibernate提供的Session的增删改查方法来操作对象

User user = new User();
user.setPassword("123456");
user.setUserName("test");
//获取加载配置管理类
Configuration configuration = new Configuration();
configuration.configure();
//创建Session工厂
SessionFactory sessionFactory = configuration.buildSessionFactory();
//得到Session
Session session = sessionFactory.openSession();
//开启事务,得到事务对象
Transaction transaction = session.getTransaction();
//开启事务
transaction.begin();
session.save(user);
//提交事务
transaction.commit();
//关闭session
session.close();

我们操作对象就和操作数据库数据一样,Hibernate会自动帮我们生成SQL语句(可以屏蔽不同数据库的差异),自动进行映射。这样我们的代码变得简洁了,可读性也提高了。

但是Hibernate在业务复杂的项目中也存在一些问题:

  1. 比如使用get()、save()、update()这些方法操作对象的时候,实际操作的是所有字段,没有办法指定部分字段,不够灵活。
  2. 这种自动生成SQL的方式,如果我们想要对SQL做一些优化,是非常困难的,也就是说可能会出现性能比较差的SQL。
  3. 不支持动态SQL,比如分表中的表名的变化等。

4、 MyBatis

半自动化的MyBatis可以解决上面Hibernate中存在的几个问题,所谓的"半自动化"是相对于Hibernate这种全自动化的框架来说的,也就是MyBatis的封装性没有Hibernate那么高,不会自动生成全部的SQL语句,主要解决的是SQL和对象的映射问题。

在MyBatis中, SQL和代码是分离的,所以可以说只要会写SQL,就可以用MyBatis,学习成本比较低。
那么MyBatis可以解决那些问题呢?

  1. 可以使用连接池对连接进行管理
  2. SQL和代码分离,可以对SQL集中管理,便于维护
  3. 可以处理结果集和实体类的映射
  4. 可以解决参数映射和动态SQL
  5. SQL的重复使用
  6. 缓存机制
  7. 插件机制

当然,MyBatis和DBUtils、Spring JDBC以及Hibernate一样,都是对JDBC进行了封装。如果我们去看源码,一定可以找到对Statement以及ResultSet这些对象。那么这么多种工具和框架,我们在项目中要如何选择呢?

  1. 在一些业务逻辑比较简单,表数量比较少,关系比较简单的项目,可以选择使用Hibernate或者JPA
  2. 如何需要更加灵活的SQL,可以使用MyBatis
  3. 对于底层的编码,或者性能要求非常高的场景,可以直接使用JDBC

彩蛋

点击下方链接,可以免费获取大量电子书资源
免费领取

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1365352.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

进阶学习——引导过程和服务控制

目录 一、引导过程 1.开机自检BIOS 2.MBR引导 3.GRUB菜单 4.加载Linux内核 5.init进程初始化 6.Centos启动过程总结 7.系统初始化进程 7.1init进程 7.2Systemd 7.2.1Systemd单元类型 7.2.2运行级别所对应的Systemd目标 二、服务控制 1.修复MBR扇区故障 1.1实验操…

前端超好玩的小游戏合集来啦--周末两天用html5做一个3D飞行兔子萝卜小游戏

文章目录 💖飞行兔子萝卜小游戏💟效果展示💟代码展示源码获取💖飞行兔子萝卜小游戏 💟效果展示 💟代码展示 <body> <script src=

欢乐钓鱼^^

欢迎来到程序小院 欢乐钓鱼 玩法&#xff1a;点击鼠标左键左右晃动的鱼钩&#xff0c;下方左右移动的鱼对准鱼的方向即可进行钓鱼&#xff0c; 不同的鱼不同的分数&#xff0c;快去钓鱼吧^^开始游戏https://www.ormcc.com/play/gameStart/241 html <div id"gamediv&qu…

力扣labuladong一刷day54天前缀树

力扣labuladong一刷day54天前缀树 文章目录 力扣labuladong一刷day54天前缀树一、208. 实现 Trie (前缀树)二、648. 单词替换三、211. 添加与搜索单词 - 数据结构设计四、1804. 实现 Trie &#xff08;前缀树&#xff09; II五、677. 键值映射 一、208. 实现 Trie (前缀树) 题…

[答疑]漏斗图,领域驱动设计叒创新了?

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 albert 2024-1-1 21:11 这篇文章说用DDD重构****&#xff0c;演示了一种漏斗图&#xff0c;请教潘老师&#xff0c;这个图是DDD提出来的吗&#xff0c;您怎么评价&#xff1f; UMLChi…

系列三十五、获取Excel中的总记录数

一、获取Excel中的总记录数 1.1、概述 使用EasyExcel开发进行文件上传时&#xff0c;通常会碰到一个问题&#xff0c;那就是Excel中的记录数太多&#xff0c;使用传统的方案进行文件上传&#xff0c;很容易就超时了&#xff0c;这时可以通过对用户上传的Excel中的数量进行限制…

优雅的通过Shell脚本生成Go的程序包

前言 随着Go语言的普及&#xff0c;越来越多的开发人员选择使用Go编写代码。虽然越来越多的公司项目已使用持续集成/持续部署&#xff08;CI/CD&#xff09;工具&#xff0c;用于自动化构建、测试和部署Go程序包&#xff0c;但存在一些部署在ECS服务器的Go程序包或需要手动编译…

编译原理Lab4-使用LightIR框架自动产生cminus-f语言的LLVM IR

[[#实验框架|实验框架]][[#实验过程|实验过程]] [[#实验过程#全局变量的设计|全局变量的设计]][[#实验过程#1ASTProgram|1ASTProgram]][[#实验过程#2ASTNum|2ASTNum]][[#实验过程#3ASTVarDeclaration|3ASTVarDeclaration]][[#实验过程#4ASTFunDeclaration|4ASTFunDeclaration]]…

概率论与数理统计 知识点+课后习题

文章目录 &#x1f496; [学习资源整合](https://www.cnblogs.com/duisheng/p/17872980.html)&#x1f4da; 总复习&#x1f4d5; 知识点⭐ 常用分布的数学期望和方差 &#x1f4d9; 选择题&#x1f4d9; 填空题&#x1f4d9; 大题1. 概率2. 概率3. 概率4. P5. 概率6. 概率密度…

读元宇宙改变一切笔记01_起源

1. 元宇宙是我们下一个生存之地 1.1. 1968年&#xff0c;只有不到10%的美国家庭拥有彩色电视&#xff0c;但当年票房排名第二位的电影《2001&#xff1a;太空漫游》&#xff08;2001: A Space Odyssey&#xff09;设想了这样的未来 1.1.1. 斯坦利库布里克(Stanley Kubrick) …

react-router-domV6.21.1版本结合ant design mobile的TabBar标签栏和Popup弹出层实现移动端路由配置

react-router-demo react-router-dom在V6版本之后更换了很多的API名称&#xff0c;在ant design mobile的TabBar配置中还是之前的旧版本&#xff0c;比如使用了switch组件等。我们在这里使用新版本的react-router-dom进行react移动端的配置 首先使用npm下载最新版的react-rout…

【前端设计】小球loading动画

欢迎来到前端设计专栏&#xff0c;本专栏收藏了一些好看且实用的前端作品&#xff0c;使用简单的html、css语法打造创意有趣的作品&#xff0c;为网站加入更多高级创意的元素。 html <!DOCTYPE html> <html lang"en"> <head><meta charset&quo…

YOLOv5改进 | 主干篇 | CSWinTransformer交叉形窗口网络改进特征融合层

一、本文介绍 本文给大家带来的改进机制是CSWin Transformer,其基于Transformer架构,创新性地引入了交叉形窗口自注意力机制,用于有效地并行处理图像的水平和垂直条带,形成交叉形窗口以提高计算效率。它还提出了局部增强位置编码(LePE),更好地处理局部位置信息,我将其…

File-一个IO流中至关重要的类

File类 概述&#xff1a;文件和目录路径名的抽象表示&#xff0c;File 对象就代表一个路径 对与File而言&#xff0c;其封装的并不是一个真正存在的文件&#xff0c;仅仅是一个路径名而已。它可以是存在的&#xff0c;也可以是不存在的。将来要通过具体的操作把这个路径的内容…

构建高效PythonWeb:GraphQL+Sanic

1.1 简介&#xff1a;在当今快速发展的技术时代&#xff0c;Web应用的性能和灵活性变得越来越重要。在众多技术中&#xff0c;GraphQL和Sanic以其独特的优势脱颖而出。GraphQL&#xff0c;作为一个强大的数据查询语言&#xff0c;为前端和后端之间的通信提供了极大的灵活性。而…

HCIA-Datacom题库(自己整理分类的)_09_Telent协议【13道题】

一、单选 1.某公司网络管理员希望能够远程管理分支机构的网络设备&#xff0c;则下面哪个协议会被用到&#xff1f; RSTP CIDR Telnet VLSM 2.以下哪种远程登录方式最安全&#xff1f; Telnet Stelnet v100 Stelnet v2 Stelnet v1 解析&#xff1a; Telnet 明文传输…

Oracle-expdp备份变慢问题分析

问题背景&#xff1a; 应用有一个每日跑批之前的备份作业&#xff0c;通过expdp备份应用的用户数据&#xff0c;数据量大概为600G左右&#xff0c;正常情况下可以在20分钟内跑完&#xff0c;但最近expdp备份完成时间却突然猛涨到要2小时32分才能备份完&#xff0c;导致后续的跑…

使用 MONAI 加载和保存各种格式的医学图像

本教程属于实战&#xff0c;手把手教你加载各种医学图像数据&#xff08;nii.gz, .dcm, .png等&#xff09;。并学会查看医学图像数据的元数据&#xff08;shape, affine, orientation&#xff09;。学会使用monai全方位了解你的数据&#xff0c;并把它用于之后的深度学习训练。…

最新ChatGPT网站系统源码+详细搭建部署教程+Midjourney绘画AI绘画

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作Ch…

数据版本控制利器LakeFS的介绍,以及其使用方法,与其它工具结合案例

LakeFS介绍 LakeFS 是一个开源的数据湖版本控制系统&#xff0c;可以帮助用户管理和控制数据湖中的数据版本。以下是LakeFS的一些主要用处和功能介绍&#xff1a; 数据版本控制&#xff1a;LakeFS 提供了类似于 Git 的版本控制功能&#xff0c;可以跟踪和管理数据湖中的数据版…