跳至主要內容

Mybatis 基础

Yaien Blog原创大约 10 分钟MyBatis基础

Mybatis 基础

Mybatis 基础笔记

作用

简化持久层开发,当需要开发某个增删改查功能时,程序员只需要定义好该功能对应的抽象方法,及该抽象方法的功能对应的SQL语句即可。

创建

与创建SpringMVC项目的步骤相同,另外,增加添加依赖:


<dependencys>
    <!-- MyBatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.1</version>
    </dependency>

    <!-- MyBatis整合Spring -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.1</version>
    </dependency>

    <!-- Spring-JDBC,与spring-webmvc使用相同版本 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>4.3.8.RELEASE</version>
    </dependency>

    <!-- MySQL连接 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.16</version>
    </dependency>

    <!-- 数据库连接池 -->
    <dependency>
        <groupId>commons-dbcp</groupId>
        <artifactId>commons-dbcp</artifactId>
        <version>1.4</version>
    </dependency>
</dependencys>

配置数据库连接

src/main/resources下创建db.properties文件,用于配置数据库连接:

    url=jdbc:mysql://localhost:3306/tedu_ums?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    driver=com.mysql.cj.jdbc.Driver
    username=root
    password=root
    initialSize=2
    maxActive=10

关于driver属性的配置:在mysql-connector-java的jar包中,展开,找到META-INF下的services下的java.sql.Driver
文件,该文件中记录的就是正确的驱动类,如果没有该文件,则使用com.mysql.jdbc.Driver

然后,可以在Spring的配置文件中,通过<util:properties>节点读取以上配置文件:

<!-- 读db.properties配置 -->
<util:properties id="dbConfig" location="classpath:db.properties"/>

在实际连接时,应该使用BasicDataSource对象获取连接对象,在使用之前需要为BasicDataSource的相关属性注入值:

    <!-- 配置数据源 -->
<bean class="org.apache.commons.dbcp.BasicDataSource">
    <property name="url" value="#{dbConfig.url}"/>
    <property name="driverClassName" value="#{dbConfig.driver}"/>
    <property name="username" value="#{dbConfig.username}"/>
    <property name="password" value="#{dbConfig.password}"/>
    <property name="initialSize" value="#{dbConfig.initialSize}"/>
    <property name="maxActive" value="#{dbConfig.maxActive}"/>
</bean>

以上配置中,各<property>节点的name属性的值来自BasicDataSource中的属性名称,在Spring表达式例如#{dbConfig.driver}中的driver来自db.properties
文件中各等于号左侧的属性名。

完成后,就可以编写单元测试,尝试获取连接对象:

    public class Tests {
		@Test
		public void getConnection() throws SQLException {
			ClassPathXmlApplicationContext ac
				= new ClassPathXmlApplicationContext(
					"spring.xml");
			
			DataSource dataSource
				= ac.getBean("dataSource", DataSource.class);
			
			Connection conn = dataSource.getConnection();
			System.out.println(conn);
			
			ac.close();
		}
	}

接口与抽象方法

在开发持久层功能时,需要为对应的功能定义抽象方法,这些抽象方法应该存在于接口中,所以,先创建对应的接口文件cn.tedu.mybatis.UserMapper

	public interface UserMapper {
	}

然后,在接口中添加抽象方法,关于抽象方法的设计原则:

  1. 如果方法对应的功能是执行增、删、改,可以使用Integer作为返回值类型,表示“受影响的行数”,如果不关心返回值,也可以使用void,如果方法对应的功能是查询,返回值可以根据实际使用需求来设计;
  2. 方法的名称可以自定义,但绝不可以重载;
  3. 方法的参数按需设计。

以“增加新用户数据为例”,可以设计为:

	Integer addnew(User user);

则还应该在项目补充创建cn.tedu.mybatis.User类。

然后,还需要在Spring的配置文件中,配置org.mybatis.spring.mapper.MapperScannerConfigurer类,以设置接口文件的位置:

<!-- MapperScannerConfigurer -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="cn.tedu.mybatis"/>
</bean>

配置SQL

http://doc.tedu.cn/config/mybatis-mapper.zip下载配置SQL语句的文件的压缩包,解压后得到SomeMapper.xml文件,将其重命名为UserMapper.xml

src/main/resources/下创建名为mappers的文件夹,然后将UserMapper.xml粘贴到这个文件夹中。

然后编辑UserMapper.xml,首先,添加<mapper>根节点,并配置namespace属性,取值是该XML文件对应的JAVA接口文件:

<!-- namespace:对应的接口 -->
<mapper namespace="cn.tedu.mybatis.UserMapper"></mapper>

然后,添加子级节点,以配置与接口中抽象方法对应的SQL语句,应该根据所需要执行的操作类型来决定使用<insert><delete><update><select>节点中的某一种,子点的id
属性就是对应的抽象方法的名称,然后,在节点内部配置所需要执行的SQL语句:

    <!-- id:抽象方法的名称 -->
<insert id="addnew">
    INSERT INTO t_user (
    username,password,
    age,phone,
    email
    ) VALUES (
    #{username},#{password},
    #{age},#{phone},
    #{email}
    )
</insert>

如果配置的是<select>节点,还必须配置resultType属性,用于表示返回值类型,如果返回值类型是List集合,则该属性配置值是List集合中的元素类型。

最后,还需要在Spring的配置文件中,配置org.mybatis.spring.SqlSessionFactoryBean类,用于指定:XML文件的位置,指定数据源:

    <!-- SqlSessionFactoryBean -->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
    <!-- XML文件在哪里 -->
    <property name="mapperLocations"
              value="classpath:mappers/*.xml"/>
    <!-- 用哪个数据源 -->
    <property name="dataSource"
              ref="dataSource"/>
</bean>

验证以上功能是否完成,可以编写并执行单元测试:

	@Test
	public void addnew() {
		ClassPathXmlApplicationContext ac
			= new ClassPathXmlApplicationContext(
				"spring.xml");
		
		UserMapper userMapper
			= ac.getBean("userMapper", UserMapper.class);
		
		User user = new User();
		user.setUsername("MyBatis");
		user.setPassword("888888");
		Integer rows = userMapper.addnew(user);
		System.out.println("rows=" + rows);
		
		ac.close();
	}

查询时,需要查询结果中的列名与返回值类型中的属性名保持一致

假设在t_user表中添加了新的名为is_delete的字段:

alter table t_user add column is_delete int;

则对应的User类中也应该添加新的属性,以与对应:

    public class User {
		private Integer id;
		private String username;
		private String password;
		private Integer age;
		private String phone;
		private String email;
		private Integer isDelete;

当查询数据时,需要自定义别名,使得查询结果中的列名与返回值类型中的属性名保持一致:


<select id="findById"
        resultType="cn.tedu.mybatis.User">
    SELECT
    id,username,
    password,age,
    phone,email,
    is_delete AS isDelete
    FROM
    t_user
    WHERE
    id=#{id}
</select>

简单的关联表查询数据

假设需要创建“部门信息表”:

	CREATE TABLE t_department (
		id INT AUTO_INCREMENT COMMENT '部门id',
		name VARCHAR(30) UNIQUE NOT NULL COMMENT '部门名称',
		PRIMARY KEY(id)
	) DEFAULT CHARSET=utf8;

然后,添加一些模拟数据:

	INSERT INTO t_department (name) VALUES ('软件研发部'),('人力资源部'),('财务部'),('销售部');

并且,每个用户都归属于某个部门:

	ALTER TABLE t_user ADD COLUMN department_id INT;

最后,为用户分配部门:

	UPDATE t_user SET department_id=1 WHERE id IN (21,28,32);
	UPDATE t_user SET department_id=2 WHERE id IN (24,25,26);
	UPDATE t_user SET department_id=3 WHERE id IN (22,27,30);
	UPDATE t_user SET department_id=4 WHERE id IN (23,29,31);

假设存在需求“查询某用户的信息,并显示该用户的部门的名称”,必须通过关联查询才可以得到所需要的结果,需要执行的SQL语句大致是:

	select 
		* 
	from 
		t_user 
	left join 
		t_department 
	on 
		t_user.department_id=t_department.id 
	where 
		t_user.id=25;

如果需要使用MyBatis开发该功能,首先,目前并没有某个类型可以封装查询结果!因为创建的User类是与t_user表相对应的,例如User这样的类称之为实体类(entity)
,实体类肯定不满足多表查询需求的,所以,对于这种情况,需要自行另创建VO类(value object),这种类的设计应该与查询结果或查询需求相对应:

	public class UserVO {
		private Integer id;
		private String username;
		private String password;
		private Integer age;
		private String phone;
		private String email;
		private Integer isDelete;
		private Integer departmentId;
		private String departmentName;
	}

VO类的设计方式与实体类基本一致,只是这2种类的定位不同而已,实体类是与数据表对应的,VO类是与查询结果对应的。

然后,可以将此前的findById(Integer id)的返回值类型修改为UserVO

	UserVO findById(Integer id);

且映射的配置:


<select id="findById"
        resultType="cn.tedu.mybatis.UserVO">
    SELECT
    t_user.id,username,
    password,age,
    phone,email,
    is_delete AS isDelete,
    department_id AS departmentId,
    name AS departmentName
    FROM
    t_user
    LEFT JOIN
    t_department
    ON
    t_user.department_id=t_department.id
    WHERE
    t_user.id=#{id}
</select>

使用resultMap处理1对多的关联查询

假设存在需求“查询某部门信息,且显示出该部门所有的员工”,需要执行的SQL语句大致是:

	select 
		*
	from
		t_department
	left join
		t_user
	on
		t_department.id=t_user.department_id
	where
		t_department.id=?

这类查询可能查到多条结果,取决于该部门的员工数量,但是,查询的需求却是查询“某1个”部门的信息!在设计查询的方法时,就需要某1个类型可以封装多条查询结果,可以:

	public class DepartmentVO {
		private Integer id;
		private String name;
		private List<User> users;
	}

则对应的抽象方法可以是:

	public interface DepartmentMapper {
		DepartmentVO findById(Integer id);
	}

此次查询必然可能出现多个结果,MyBatis并不知道如何将多个结果封装到1个对象中,所以,按照此前的查询做法,必然会出错!例如:

	Caused by: org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 3

在这种情况下,需要自定义<resultMap>节点,用于指导MyBatis如何将结果进行封装:


<mapper>
    <!-- id:自定义名称 -->
    <!-- type:返回结果的类型 -->
    <resultMap id="DepartmentVO_Map"
               type="cn.tedu.mybatis.DepartmentVO">
        <!-- id节点:用于配置主键 -->
        <!-- result节点:用于配置非主键 -->
        <!-- column:查询结果的列名 -->
        <!-- property:返回结果类型中的属性名 -->
        <!-- 无论哪个节点,都是用于告之MyBatis将查询结果中哪一列的数据放到返回类型中的哪个属性中 -->
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <!-- collection节点:用于配置1对多关系 -->
        <!-- ofType:集合中的元素类型 -->
        <collection property="users"
                    ofType="cn.tedu.mybatis.User">
            <id column="uid" property="id"/>
            <result column="username" property="username"/>
            <result column="password" property="password"/>
            <result column="age" property="age"/>
            <result column="phone" property="phone"/>
            <result column="email" property="email"/>
            <result column="is_delete" property="isDelete"/>
        </collection>
    </resultMap>

    <select id="findById"
            resultMap="DepartmentVO_Map">
        SELECT
        t_department.id, name,
        t_user.id AS uid, username,
        password, age,
        phone, email,
        is_delete
        FROM
        t_department
        LEFT JOIN
        t_user
        ON
        t_department.id=t_user.department_id
        WHERE
        t_department.id=#{id}
    </select>
</mapper>

查询结果例如:

	DepartmentVO [
		id=1, name=软件研发部, 
		users=[
			User [id=21, username=Mike01, password=8888, age=18, phone=13800138001, email=Mike@qq.com, isDelete=0], 
		
			User [id=28, username=Mike08, password=8888, age=25, phone=13800138001, email=Mike@qq.com, isDelete=0], 
	
			User [id=32, username=Mike11, password=8888, age=26, phone=13800138001, email=Mike@qq.com, isDelete=0]
		]
	]

动态SQL

动态SQL指的是可以在配置SQL语句添加一些特殊的标签,例如<if><foreach>等,可以根据参数的不同,最终生成不同的SQL语句,则称之为动态SQL。

例如:根据若干个id删除数据,大致的SQL语句是:


	delete from t_user where id in (1,3,5,7,9)

在实际应用中,以上IN关键字后侧的括号中的值是不确定的,不光是值本身,值的数量也是不确定,并且各值之间需要使用逗号进行分隔,当值的数量不确定时,逗号的数量也是无法确定的!

首先,需要在UserMapper.java接口中添加抽象方法:

	Integer deleteByIds(List<Integer> ids);

在设计参数时,可以使用List集合类型,也可以使用数组类型。

然后,在UserMapper.xml中配置以上抽象方法的映射:


<delete id="deleteByIds">
    DELETE FROM t_user
    WHERE id IN (
    <foreach collection="list"
             item="id" separator=",">
        #{id}
    </foreach>
    )
</delete>

在配置<foreach>节点时:

  • collection:需要被遍历的集合或数据,如果抽象方法只有1个参数时,如果参数的类型是List集合,则取值为list,如果参数类型是数组,则取值为array
    ;如果抽象方法有多个参数,则该属性取值为@Param("xx")注解中使用的名称。

  • item:遍历过程中,集合中的元素的名称,在<foreach>子级位置,可以使用#{item值}表示被遍历到的元素的值。

  • separator:分隔符。

  • openclose:遍历生成的SQL语句部分的最左侧字符和最右侧字符。

#{}与${}占位符

在MyBatis中,配置SQL语句时,可以使用#{}${}这2种占位符。

使用#{}占位符,可以用于占位某些值,也就是在SQL中写值的位置,都可以使用这种占位符(此前在学习JDBC时使用?的位置);而${}可以表示SQL语句的任何部分!

在使用#{}对某个值进行占位时,框架对整个SQL语句是有预编译处理的,无需考虑该值的数据类型的问题;而使用${}
占位时,框架的处理方式其实就是非常单纯的字符串拼接,需要考虑数据类型的问题,如果占位的值中包括字符串类型的值,则必须使用''框住值!

由于#{}只能对某个值进行占位,SQL语句本身是相对固定的,所以,这种做法实现的功能的局限性就非常明显,由于是预编译的,没有SQL注入风险,且工作效率较高!而${}
可以随意占位,功能可以非常灵活,但是,不是预编译的,有SQL注入风险,工作效率较低。

上次编辑于:
贡献者: yanggl