SSM之Mybatis

1 Mybatis

1.1 概述

MyBatis 源于 Apache 的一个开源项目 iBatis,而 iBatis 一词则来源于“internet”和“abatis”的组合,2010年这个项目由 Apache Software Foundation 迁移到了 Google Code,并且改名为MyBatis ,2013年11月其又迁移到 Github。Mybatis是一款基于Java的半自动的ORM持久层框架,具有较高的SQL灵活性,支持高级映射(一对一,一对多),动态SQL,延迟加载和缓存等特性,但它的数据库无关性较低。相比于基于面向对象使用 HQL 语言的 Hibernate 框架,MyBatis 则基于 SQL 面向结果集,因此其效率更高。

1.2 ORM介绍

ORM即Object Relation Mapping,对象关系映射。对象指的就是Java里的对象,关系指的就是数据库中的关系模型,对象关系映射,指的就是在Java对象和数据库的关系模型之间建立一种对应关系。就比如Java里面的一个汽车Car类,就对应数据库中的汽车car表,类中的属性和表中的字段一对一对应(汽车Car类里面如果有汽车颜色color属性,那么就应该与汽车car数据表里面的汽车颜色color字段对应)。Student类就对应student表,一个Student对象就对应student表中的一行数据(一个对象里包含对象的所有属性,一行数据包含数据表里的所有字段)。


1.3 为什么Mybatis是半自动的ORM持久层框架?  

因为用mybatis进行开发,我们还需要手动编写SQL语句。而全自动的ORM框架,如hibernate,则不需要编写SQL语句。用hibernate开发,只需要定义好ORM映射关系,就可以直接进行CRUD操作了。但也是由于mybatis需要手写SQL语句,所以它mybatis有较高的灵活性,可以根据需要,自由地对SQL进行定制,也因为要手写SQL,当要切换数据库时,SQL语句可能就要重写,因为不同的数据库有不同的方言(Dialect),所以mybatis的数据库无关性低。虽然mybatis需要手写SQL,但相比JDBC,它提供了输入映射和输出映射,可以很方便地进行SQL参数设置,以及结果集封装。并且还提供了关联查询和动态SQL等功能,极大地提升了开发的效率。

1.4 Mybatis优点与缺点

优点:

  1. 基于SQL语言编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理,提供XML标签,支持编写动态SQL语句,并可重用。
  2. 与JDBC相比,减少了50%以上代码量,消除了JDBC大量冗余的代码,不需要手动开关连接。
  3. 很好的与各种数据库兼容(因为mybatis使用了JDBC来连接数据库,所以只要jdbc支持的数据库,mybatis同样也支持)。
  4. 能够与Spring很好的集成。
  5. 提供映射标签,支持对象与数据库的ORM字段关系映射,提供对象关系映射标签,支持对象关系组件维护。

 缺点:

  1. SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。
  2. SQL语句依赖于数据库,导致数据库移植性较差,不能随意更换数据库。 

1.5 Mybatis内部组件结构图

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTI5MzI4NzY=,size_16,color_FFFFFF,t_70

1.6 操作步骤 

  1. 编写全局配置文件
  2. 编写mapper映射文件
  3. 加载全局配置文件,生成SqlSessionFactory
  4. 创建SqlSession,调用mapper映射文件中的SQL语句来执行CRUD操作

 2 入门案例

2.1 XML映射方式

2.1.1 项目结构图

20210719194258580.png

 这里准备了两个类,一个是User类,一个是Dept类,前面已经提过,类中的属性应与数据表中的字段一一对应,所以还需准备两个数据表,并且数据表里的字段要与类中的属性成对应关系。两个数据表的结构与内容如下:

user表:

20210719195000846.png

dept表:

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L05vdmljZVo=,size_16,color_FFFFFF,t_70

2.1.2 添加依赖包

打开本module项目的pom.xml文件,向里面添加依赖,主要添加两个依赖包,一个是连接数据库的jdbc依赖包,一个是mybatis依赖包。

<dependencies>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.19</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
    </dependencies>

 2.1.3 创建核心配置文件

在resources目录下创建核心配置文件mybatis-config.xml,即全局配置文件,里面包含了连接数据库的url、用户名、密码等信息,内容如下:

<?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">

<!-- mybatis的核心配置文件 -->
 <configuration>
    <typeAliases>
        <!--给对应类的配置文件中的resultType参数起别名-->
        <typeAlias type="cn.tedu.pojo.User" alias="User"></typeAlias>
        <typeAlias type="cn.tedu.pojo.Dept" alias="Dept"></typeAlias>
    </typeAliases>

    <!--可以配置多个数据库连接的环境,使用default指定默认用哪个-->
 	<environments default="test">
        <!--配置了具体连接数据库的参数-->
 		<environment id="test">
            <!--使用的事物管理器-->
 			<transactionManager type="JDBC"></transactionManager>
            <!--数据源:就是制定了数据库连接时的参数-->
 			<dataSource type="POOLED">
 			<property name="driver" value="com.mysql.cj.jdbc.Driver"/> 
            <property name="url" value="jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf8&amp;serverTimezone=Asia/Shanghai" /> 
				<property name="username" value="root"/> 
				<property name="password" value="root"/> 
 			</dataSource>
 		</environment>
 	</environments>
    <!--引入对应类的配置文件-->
     <mappers>
        <mapper resource="UserMapper.xml"/>
        <mapper resource="DeptMapper.xml"/>
    </mappers>
</configuration>

 2.1.4 创建对应类的配置文件

同样在resources目录下创建对应类的配置文件,编写mapper映射文件,文件里主要包含要用到的SQL语句。

UserMapper.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命名空间,值唯一,用来作为文件的唯一标识-->
<mapper namespace="hello">
<!--id表示SQL注入唯一标识,因为可能有多个SQL注入,内容代表指定参数-->
<sql id="par">
	id,name,addr,age
</sql>

<!--id代表要执行的SQL,select标签代表查询,还有其它标签,如delete删除标签-->
<!--resultType结果集,用来完成orm,把表里的字段自动映射给指定类里的属性,这里在核心配置文件中使用了alias对resultType的参数起了别名User,否则使用全路径cn.tedu.pojo.User-->
<select id="qurAll" resultType="User">
	select <include refid="par"></include>
	from user
</select>
<select id="getByName" resultType="User">
	select * from user where name = #{name}
</select>
<select id="getCountByAddr" resultType="Integer">
	select count(1) from user where addr = #{addr}
</select>
<select id="qurById" resultType="User">
	select
	<include refid="par"></include>
	from user where
	<if test="id > 2">id = ${id}</if>
    <!--使用了if条件判断,如果传入的id>2才执行该SQL语句-->
</select>
</mapper>

2.1.5 测试结果

在test目录下创建测试类TestUser。

package cn.tedu.test;

import cn.tedu.pojo.User;
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 org.junit.Test;
import java.io.InputStream;
import java.util.List;
public class TestUser {
    @Test
    public void get() throws Exception{
        //1,创建SqlSessionFactory对象,线程非安全,用来产生SqlSession
        //2,创建SqlSession,用来执行sql
        //3, 定位SQL: namespace的值+id的值
        //4,解析结果并打印
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory session = new SqlSessionFactoryBuilder().build(in);
        SqlSession sqlsession = session.openSession();
        //查询user表里的所有内容
        List<User> list = sqlsession.selectList("hello.qurAll");
        for (User u:list){
            System.out.println(u);
        }
        //查询name=“xiongda”的所有内容
        User ur = sqlsession.selectOne("hello.getByName","xiongda");
        System.out.println(ur);
        //结果可能不止一个
        List<User> list3 = sqlsession.selectList("hello.getByName","xiongda");
        for (User u:list3){
            System.out.println(u);
        }
        //查询addr=“上海”的内容的数量
        int count = sqlsession.selectOne("hello.getCountByAddr", "上海");

    }
}

相关结果:
20:38:46.314 [main] DEBUG hello.qurAll - ==>  Preparing: select id,name,addr,age from user
20:38:46.356 [main] DEBUG hello.qurAll - ==> Parameters: 
20:38:46.391 [main] DEBUG hello.qurAll - <==      Total: 3
User{id=1, name='hanmeimei', addr='北京', age=28}
User{id=2, name='xiongda', addr='上海', age=20}
User{id=3, name='xiaonger', addr='上海', age=19}
20:38:46.393 [main] DEBUG hello.getByName - ==>  Preparing: select * from user where name = ?
20:38:46.393 [main] DEBUG hello.getByName - ==> Parameters: xiongda(String)
20:38:46.395 [main] DEBUG hello.getByName - <==      Total: 1
User{id=2, name='xiongda', addr='上海', age=20}
20:38:46.396 [main] DEBUG hello.getCountByAddr - ==>  Preparing: select count(1) from user where addr = ?
20:38:46.396 [main] DEBUG hello.getCountByAddr - ==> Parameters: 上海(String)
20:38:46.398 [main] DEBUG hello.getCountByAddr - <==      Total: 1
2
20:38:46.427 [main] DEBUG hello.qurById - ==>  Preparing: select id,name,addr,age from user where id = 3
20:38:46.428 [main] DEBUG hello.qurById - ==> Parameters: 
20:38:46.429 [main] DEBUG hello.qurById - <==      Total: 1
User{id=3, name='xiaonger', addr='上海', age=19}

tips:

  1. 编写mapper.xml,书写SQL,并定义好SQL的输入参数,和输出参数
  2. 编写全局配置文件,配置数据源,以及要加载的mapper.xml文件
  3. 通过全局配置文件,创建SqlSessionFactory
  4. 每次进行CRUD时,通过SqlSessionFactory创建一个SqlSession
  5. 调用SqlSession上的selectOne,selectList,insert,delete,update等方法,传入mapper.xml中SQL标签的id,以及输入参数
     

2.2 mapper.xml的SQL语句中的占位符${}和#{}

一般会采用#{},#{}在mybatis中,最后会被解析为?,其实就是Jdbc的PreparedStatement中的?占位符,它有预编译的过程,会对输入参数进行类型解析(如果入参是String类型,设置参数时会自动加上引号),所以#{}可以防止SQL注入,如果parameterType属性指定的入参类型是简单类型的话(简单类型指的是8种java原始类型再加一个String),#{}中的变量名可以任意,如果入参类型是pojo,比如是Student类

public class Student{
    private String name;
    private Integer age;
    //setter/getter
}

那么#{name}表示取入参对象Student中的name属性,#{age}表示取age属性,这个过程是通过反射来做的,这不同于${},${}取对象的属性使用的是OGNL(Object Graph Navigation Language)表达式。而${},一般会用在模糊查询的情景,比如SELECT * FROM student WHERE name like '%${name}%';它的处理阶段在#{}之前,它不会做参数类型解析,而仅仅是做了字符串的拼接,若入参的Student对象的name属性为zhangsan,则上面那条SQL最终被解析为SELECT * FROM student WHERE name like '%zhangsan%';而如果此时用的是SELECT * FROM student WHERE name like '%#{name}%'; 这条SQL最终就会变成SELECT * FROM student WHERE name like '%'zhangsan'%'; 所以模糊查询只能用${},虽然普通的入参也可以用${},但由于${}不会做类型解析,就存在SQL注入的风险,比如SELECT * FROM user WHERE name = '${name}' AND password = '${password}'我可以让一个user对象的password属性为'OR '1' = '1,最终的SQL就变成了SELECT * FROM user WHERE name = 'yogurt' AND password = ''OR '1' = '1',因为OR '1' = '1'恒成立,这样攻击者在不需要知道用户名和密码的情况下,也能够完成登录验证。另外,对于pojo的入参,${}中获取对象属性的语法和#{}几乎一样,但${}在mybatis底层是通过OGNL表达式语言进行处理的,这跟#{}的反射处理有所不同。对于简单类型(8种java原始类型再加一个String)的入参,${}中参数的名字必须是value。

#{}之所以能防止SQL注入的原因主要是因为它不会产生字符串拼接,它会遍历传入的每个字符,对特殊字符会进行转义操作,遍历完之后会对整体字符串加单引号(其实是在遍历前后分别加了一个单引号)。所以不会产生注入问题。

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值