MyBatis框架

MyBatis

1.框架的作用

        在Java实现数据库编程时,主要通过【JDBC】来实现,而JDBC相关代码在实现过程中【流程相对固定】,不论哪种数据操作,代码方面差距不大,所以,就出现了各种简化开发的持久层框架,常见的有【Hibernate】和【MyBatis】。MyBatis是持久层框架,使用MyBatis框架时,无需编写JDBC相关代码,只需要为某些抽象方法配置其对应的需要执行的SQL语句即可。

2.使用MyBatis框架实现数据访问

2.1 创建数据库与数据表

CREATE TABLE t_user (
		id INT AUTO_INCREMENT,
		username VARCHAR(20) UNIQUE NOT NULL,
		password VARCHAR(20) NOT NULL,
		age INT,
		phone VARCHAR(20),
		email VARCHAR(20),
		PRIMARY KEY(id)
	) DEFAULT CHARSET=UTF8;

2.2 创建项目
     生成部署描述文件【web.xml】,添加【Tomcat环境】,【添加依赖】,配置【web.xml】文件,配置文件【spring-dao.xml】,创建数据库连接配置文件【db.properties】

添加相关依赖

<dependencies>
  	<!-- 添加Spring相关依赖 -->
        <!-- 在同一个项目中,如果需要使用多个以spring-作为前缀的依赖,则应该使用相同的版本 -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-webmvc</artifactId>
		<version>4.3.10.RELEASE</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-jdbc</artifactId>
		<version>4.3.10.RELEASE</version>
	</dependency>
		
  	<!-- 添加MyBatis依赖 -->		
	<dependency>
		<groupId>org.mybatis</groupId>
		<artifactId>mybatis</artifactId>
		<version>3.5.0</version>
	</dependency>

	<!-- 将Spring与MyBatis整合 -->		
	<dependency>
		<groupId>org.mybatis</groupId>
		<artifactId>mybatis-spring</artifactId>
		<version>2.0.0</version>
	</dependency>
		
	<!-- 添加数据库连接池依赖 -->		
	<dependency>
		<groupId>commons-dbcp</groupId>
		<artifactId>commons-dbcp</artifactId>
		<version>1.4</version>
	</dependency>
		
	<!-- 添加单元测试依赖 -->		
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<version>4.12</version>
	</dependency>
		
	<!-- 添加依赖便于操作数据库 -->	
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.6</version>
	</dependency>
</dependencies>

配置【web.xml】中的【DispatcherServlet】和【CharacterEncodingFilter】

 <!-- 配置前端处理器 --> 
 <servlet>
  	<servlet-name>SpringMVC</servlet-name>
  	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  	<load-on-startup>1</load-on-startup>
  	<init-param>
  		<param-name>contextConfigLocation</param-name>
  		<param-value>classpath:spring-mvc.xml</param-value>
  	</init-param>
  </servlet>
  <servlet-mapping>
  	<servlet-name>SpringMVC</servlet-name>
  	<url-pattern>*.do</url-pattern>
  </servlet-mapping>
  
 <!-- 配置SpringMVC框架自带的字符编码过滤器 -->
 <filter>
	<filter-name>CharacterEncodingFilter</filter-name> 
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
    	<param-name>encoding</param-name>
    	<param-value>UTF-8</param-value>
    </init-param>
 </filter>
<filter-mapping>
	<filter-name>CharacterEncodingFilter</filter-name> 
	<url-pattern>/*</url-pattern>	
</filter-mapping> 

在source文件夹下新建配置文件【spring-dao.xml】

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context" 
	xmlns:jdbc="http://www.springframework.org/schema/jdbc"  
	xmlns:jee="http://www.springframework.org/schema/jee" 
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:aop="http://www.springframework.org/schema/aop" 
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:util="http://www.springframework.org/schema/util"
	xmlns:jpa="http://www.springframework.org/schema/data/jpa"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
		http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd
		http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
		http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
		http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd">
				
		<!-- 读取db.properties中的配置 -->
		<util:properties id="db" location="classpath:db.properties" />
		<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
			<property name="url" value="#{db.url}" />
			<property name="driverClassName" value="#{db.driver}" />
			<property name="username" value="#{db.username}" />
			<property name="password" value="#{db.password}" />
			<property name="initialSize" value="#{db.initialSize}" />
			<property name="maxActive" value="#{db.maxActive}" />
		</bean>
		<!-- 配置会话工厂SqlSessionFactory -->
		<bean class="org.mybatis.spring.mapper.SqlSessionFactory">
			<!-- 指定XML文件的位置 -->
			<property name="mapperLocations" value="classpath:mappers/*.xml" />
                        <!-- 使用哪个数据源 ref与上边数据源配置id一致-->
                        <property name="dataSource" ref="dataSource" />
		</bean>
</beans>

数据库连接配置文件【db.properties】

url=jdbc:mysql://localhost:3306/hzxh_ums?userUnicode=true&characterEncoding=utf-8
driver=com.mysql.jdbc.Driver
username=root
password=root
initialSize=3
maxActive=5

单元测试,获取连接对象

public class ConnectionTest {

	BasicDataSource dataSource;
	
	@Test
	public void getConnection() throws SQLException {
		//加载Spring配置文件,获取Spring容器
		AbstractApplicationContext ac = new ClassPathXmlApplicationContext("spring-dao.xml");
		//从Spring容器中获取对象
		dataSource = ac.getBean("dataSource",BasicDataSource.class);
		//获取连接对象
		Connection conn = dataSource.getConnection();
		//测试输出
		System.out.println(conn);
		//释放资源
		ac.close();
	}

}

2.3 接口与抽象方法

创建User实体类,在类中声明属性,与数据库中属性对应,数据库中每张表都要有一实体类与之对应

public class User implements Serializable{

	private Integer id;
	private String username;
	private String password;
	private Integer age;
	private String phone;
	private String email;

    //添加get/set/toString方法
    //根据id区分不同User对象,针对id重写hashCode及equals方法

}

创建【UserMapper】接口,并在接口中声明方法

       在使用MyBatis时,如果抽象方法对应需要执行的数据操作是增、删、改,则抽象方法的返回值应该是【Integer】,表示受影响的行数;抽象方法的名称可以自由定义,符合语法规范并能表达语义即可;抽象方法的参数应该根据执行SQL语句时所需要的参数来决定。

public interface UserMapper {
	/**
	 * 插入用户数据
	 * @param user 用户数据
	 * @return 受影响行数
	 */
	Integer addNew(User user);

	/**
	 * 根据id删除数据
	 * @param id 用户的id
	 * @return 受影响的行数
	 */
	Integer deleteById(Integer id) ;
	
	/**
	 * 修改用户密码
	 * @param id 用户id
	 * @param password 要修改的密码
	 * @return 受影响的行数
	 */
	Integer updatePassword(Integer id,String password);

}

 在Spring配置文件中配置接口扫描【MapperScannerConfigurer】,用于扫描接口文件

<!-- MapperScannerConfigurer -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
	<!-- 指定接口文件所在的根包 -->
	<property name="basePackage" value="cn.hzxh.mybatis" />
</bean>

配置映射

      在【resource】下创建名为【mappers】的文件夹,并在该文件夹下创建映射配置文件【UserMapper.xml】

<?xml version="1.0" encoding="UTF-8" ?>  
<!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"      
 "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
 <!-- namespace:匹配java的接口 -->
<mapper namespace="cn.hzxh.mybatis.mapper.UserMapper">
	<!-- 根据要执行的SQL语句的类型选择节点 -->
	<!-- id:对应抽象方法的名称 -->
	<!-- 在SQL语句中的?应该使用#{ }来占位 -->
	<!-- 在#{ }中的名称是参数User类中的属性名 -->

	<insert id="addNew">
		INSERT INTO t_user(
			username,password,age,phone,email
		)VALUES(
			#{username},#{password},#{age},#{phone},#{email}
		)
	</insert>

        <!-- insert节点可以自动配置自增主键,需要加入以下配置 -->
        <!-- useGenerateKeys表示要不要获取自动编号的值 -->
        <!-- keyProperties表示获取以后的值封装在试题类中的哪个属性中 -->
        <insert id="insert" useGenerateKeys="true" keyProperty="id" >
	
	<delete id="deleteById">
		DELETE FROM 
			t_user 
		WHERE 
			id = #{id}
	</delete>

	<update id="updatePassword">
		UPDATE 
			t_user 
		SET 
			password = #{password}
		WHERE
			id = #{id}
	</update>

</mapper>

为了使得MyBatis框架能找到这些XML的映射文件并执行其中的SQL语句,还需要在Spring-dao.xml文件中添加配置

<bean class="org.mybatis.spring.SqlSessionFactoryBean">
	<!-- 映射XML文件的位置 -->
	<property name="mapperLocations" value="mappers/*.xml" />
	<!-- 指定数据源,取值将引用(ref)以上配置的数据源的ID -->
	<property name="dataSource" ref="dataSource"/>
</bean>

可以编写测试类了

@Test
public void addnew() {

	AbstractApplicationContext ac = new ClassPathXmlApplicationContext("spring-dao.xml");		
	UserMapper userMapper = ac.getBean("userMapper", UserMapper.class);		

	User user = new User();
	user.setUsername("root");
	user.setPassword("1234");
	user.setAge(18);
	user.setPhone("13800138001");
	user.setEmail("root@tedu.cn");
	Integer rows = userMapper.addnew(user);
	System.out.println("rows=" + rows);
		
	ac.close();
}

2.4.多参数的处理

      MyBatis框架只识别抽象方法中的1个参数,即:抽象方法应该最多只有1个参数!当抽象方法只有一个参数时,如:
              public void deleteById(Integer id);
此时,映射文件中占位符号可以写任意值,如:
            <delete id="deleteById">
                     DELETE FROM t_user WHERE id=#{helloworld}
            </delete>
因为参数只有1个,框架会自动的读取对应位置的参数并应用于执行SQL语句,所以,参数的名称根本就不重要,另外,每个【.java源文件】在被编译成为【.class文件】之后,也都会丢失参数名称!当有多个参数时,一般的处理方式可以将抽象方法的参数设计为1个Map,则可以满足【参数只有1个】的需求,根据参数名称向该Map中put对应的属性值,但这种方式使用时非常不方便,因为方法的调用者并不明确向Map中封装数据时应该使用什么名称的key!
一般使用以下的方法:
       在设计抽象方法时,允许使用多个参数,但是,每个参数之前都应该添加该注解,并在注解中设置参数的名称,MyBatis框架在处理时,会将这些参数封装为一个Map,也符合框架只有一个参数的约束,同时代码也能变的易于理解。

/**
 * 修改用户密码
 * @param id 用户id
 * @param password 要修改的密码
 * @return 受影响的行数
 */
Integer updatePassword(
		@Param("id") Integer id,
		@Param("password") String password);
//注解中的值要与配置文件中占位符号的参数名称保持一致

最终测试类

public class UserMapperTestCase {

	public UserMapper userMapper;
	public AbstractApplicationContext ac;
	
        //@Before注解:执行@Test方法之前执行
	@Before
	public void doBefore() {
		 ac = new ClassPathXmlApplicationContext("spring-dao.xml");
		 userMapper = ac.getBean("userMapper",UserMapper.class);
	}

	@Test
	public void testUpdate() {
		
		AbstractApplicationContext ac = new ClassPathXmlApplicationContext("spring-dao.xml");
		UserMapper userMapper = ac.getBean("userMapper",UserMapper.class);
		//两个参数,数据正常修改
                Integer row = userMapper.updatePassword(1, "123123");
		System.out.println(row);		
	}
	
        //@After注解:执行@Test方法之后执行
	@After
	public void doAfter() {
		ac.close();
	}	
	
}

2.5 查询方法

      查询方法在配置文件【spring-dao.xml】中配置的节点为【select】节点

<!-- 查询结果为单一对象 -->
<select id="findById" resultType="cn.hzxh.mybatis.User">
	SELECT 
	    id,username,password,age,phone,email
	FROM
	    t_user
	WHERE 
	    id = #{id}
</select>
<!-- 查询结果为集合 -->
<select id="findAll" resultType="cn.hzxh.mybatis.User">
	SELECT 
	    id,username,password,age,phone,email
	FROM
	    t_user
</select>

需注意的是:无论多么简单的返回值类型,都必须配置【resultType】或【resultMap】。当抽象方法的返回值类型为【List】时,MyBatis框架底层会调用【selectOne】方法,查询结果依然是实体类的对象,而且会将查询结果封装为一个集合,因此查询结果为多条数据时,返回值类型也不需要改变。

【Mapper】接口中配置的抽象方法为

/**
 * 通过id查询用户
 * @param id 用户id
 * @return 有数据返回该用户,没有数据返回null值
 */
User findById(Integer id);
	
/**
 * 查询所有用户
 * @return 所有数据的集合
 */
List<User> findAll();

  在配置抽象方法时,返回值的类型应该根据查询需求来决定,即:调用该方法后希望得到什么的数据。如:
  获取当前表中的数据量:

Integer conutById();
<select id="countById"
	resultType="java.lang.Integer">
	SELECT COUNT(id) FROM t_user
</select>

3.动态SQL

在配置映射时,可以添加例如【if】【foreach】等节点,可以根据参数的不同,从而生成不同的SQL语句。

3.1 关于【foreach】

        如有下列需求:批量删除用户数据,需执行SQL语句为 DELETE FROM t_user WHERE id IN (3,4,5);
在MyBatis中,如果实现以上功能,首先还是要设计【抽象方法】

/**
 * 根据多个用户id删除数据
 * @param ids 用户id数组
 * @return受影响行数
 */
Integer deleteByIds(Integer[] ids);

【UserMapper.xml】文件配置

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

【foreach】中个节点中各属性的配置
--【collection】:被遍历的集合,当抽象方法只有1个参数时,取值根据参数类型选择【array】或【list】,如上文的文件配置,但有多个参数时,取值应该为【@Param】注解中的名称。如下:

/**
 * 批量修改用户密码
 * @param ids 用户数组id
 * @return 受影响行数
 */
Integer updataPasswordByIds(
		@Param("ids")Integer[] ids,
		@Param("newPassword")String newPassword) ;
<update id="updataPasswordByIds">
	UPDATE
	    t_user
	SET
	    password = #{newPassword}
	WHERE
	    id IN (
		    <foreach collection="ids" item="id" separator=",">
		    	#{id}
		    </foreach>
	    )
</update>

--【item】:遍历过程中元素的名称;
--【seperater】:分隔符,在IN语句中,分隔符为逗号",";
--【open】及【close】:遍历生成的代码区域左侧与右侧的值,如IN语句两端应为括号【 open="("  close=")"】;

3.2 关于【if】

    常用于单表查询,可以动态添加多个查询条件,如:

<select id="findUserList" resultType="cn.hzxh.mybatis.User">
	SELECT 
		*
	FROM
		t_user
	<if test="where != null">
	WHERE
		#{where}
	</if>
	<if test="orderBy != null">
	ORDER BY
		#{orderBy}
	</if>
	<if test="offset != null">
	LIMIT
		#{offset},#{count}
	</if>
</select>

       需注意的是,当需要检测的条件中,含有【?】占位符时,可以使用【#{ }】表达式,但当不含【?】占位符时,获取参数需要使用【${ }】符号,即,使用【#{ }】在执行时是【预编译】的,而【${ }】只是单纯的通过字符串拼接形成的最终的SQL语句,可能存在SQL注入风险!
归纳:在SQL语句中,查询条件为值的,要使用【#{ }】占位,为表达式的,使用【${ }】占位,如:

 SELECT uname ,age FROM t_user WHERE age = 18 LIMIT 20,10;
//WHERE 后的条件为【${where}】,LIMIT后的条件为【#{offset},#{count}】

 因此,上述配置中,【where】及【orderBy】前的符号应该改为【$】,以上查询方式几乎可以实现任何形式的单表查询,并不含聚合函数的查询需求,但此种方式不建议使用,主要原因为:
  (1)执行效率低,会查询多余的部分数据,造成性能、内存的浪费,如【SELECT *】;
  (2)使用【${ }】较多,均为字符串拼接,为防止SQL注入,会增加更多的判断,如【x'  or ' a ' = ' a '】;

对应抽象方法

/**
 * 多条件查询
 * @param where 增加查询条件
 * @param orderBy 增加排序条件
 * @param offset 增加分页信息
 * @param count 增加分页信息
 * @return 对应用户列表
 */
List<User> findUserList(
		@Param("where")String where,
		@Param("orderBy")String orderBy,
		@Param("offset")Integer offset,
		@Param("count")Integer count);

3.3 关于resultMap

      在设计查询时,【select】节点必须使用【resultType】或【resultMap】中的一项,通常多表查询,使用【resultMap】。
创建对应的【UserVO】类:

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

    // GET/SET/toString方法
	
}

设计抽象方法,返回值类型为【UserVO】:

/**
 * 根据id查找用户
 * @param id 用户id
 * @return 符合条件的用户
 */
UserVO findUserById(Integer id);

配置文件【userMapper.xml】

<select id="findUserById" resultType="cn.hzxh.mybatis.UserVO">
	SELECT
		id,username,password,age,phone,email,name AS departmentName 
	FROM 
		t_user
	LEFT JOIN
		t_department
	ON 
		t_user.did = t_department.did
	WHERE
		t_user.id = #{id}
</select>

创建测试类:

@Test
public void testUserVO() {
	Integer id = 6;
	UserVO uservo = userMapper.findUserById(id);
	System.out.println(uservo);
}

        如果查询某一个部门的详情信息,应该只返回1个结果,但通过SQL查询语句,一般都会得到2个以上的数据条数,无法赋值给一个对象,【resultMap】可以将多个查询结果封装到同一个对象中。下面介绍【resultMap】的用法。

创建数据库表【t_user】及【t_department】并完成数据初始化

创建【DepartmentVO】类

public class DepartmentVO {

	private Integer did;
	private String name;
	private List<User> users;

    //GET/SET/toString方法
}

创建【DeptMapper.xml】

<?xml version="1.0" encoding="UTF-8" ?>  
<!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"      
 "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
 <!-- namespace:匹配java的接口 -->
<mapper namespace="cn.hzxh.mybatis.mapper.DeptMapper">
	<!-- 当前resultMap用于指导mybatis将多条查询对象结果封装到同一个对象中 -->
	<resultMap id="DepartmentVOMap" type="cn.hzxh.mybatis.DepartmentVO">
		<!-- id节点:用于配置主键 -->
		<!-- column属性:查询结果中的列名 -->
		<!-- property属性:resultMap中type对应的数据类型中属性的名称 -->
		<id column="did" property="did" />
		
		<!-- result节点用于配置主键以外的其他属性 -->
		<result column="name" property="name"/>
		
		<!-- collection节点:用于配置1对多关系的数据 -->
		<!-- property属性:集合名称, ofType属性:集合中所存元素的类型 -->
		<collection property="users" ofType="cn.tedu.mybatis.User" >
			<id column="id" 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"/>
		</collection>
	</resultMap>
	
	<select id="findDepartmentById" resultMap="DepartmentVOMap">
			SELECT 
				t_department.did,id,username,password,age,phone,email,name
			FROM
				t_user
			LEFT JOIN
				t_department
			ON
				t_user.did = t_department.did 
			WHERE 
				t_department.did = #{did}
	</select>

</mapper>

在配置【resultMap】时,关于唯一字段的配置,应该使用【id】节点进行配置,虽然【result】节点也能实现相同功能,但时MyBatis是天生自带缓存的,使用【id】节点配置的数据,会作为缓存数据的标识,而使用【result】节点配置的数据则不会。

创建【DeptMapper】接口并声明抽象方法

public interface DeptMapper {
	/**
	 * 根据id查找数据
	 * @param did depart数据
	 * @return 查询结果
	 */
	DepartmentVO findDepartmentById(Integer did);
	
}

创建测试类并测试

public class DeptMapperTestCase {
	
	public DeptMapper deptMapper;
	public AbstractApplicationContext ac;
	
	@Before
	public void doBefore() {
		 ac = new ClassPathXmlApplicationContext("spring-dao.xml");
		 deptMapper = ac.getBean("deptMapper",DeptMapper.class);
	}
	
	@Test
	public void testFindDeptById() {
		Integer did = 3;
		DepartmentVO dept = deptMapper.findDepartmentById(did);
		System.out.println(dept.getUsers().size());		
	}
	
	@After
	public void doAfter() {
		ac.close();
	}
}

resultMap】另外一个作用是,当查询结果出现字段名与实体类属性名称无法对应的情况,如【SELECT * FROM t_user】,c此时,查询的结果全部为数据库字段名,不一定与实体类完全相匹配,此时需要在实体类对应的【mapper.xml】文件中配置相关节点属性。

<!-- resultMap节点:指导MyBatis如何封装查询结果 -->
<!-- id:自定义名称 -->
<!-- column:查询结果中的列名   property:实体类中的属性名 -->
<resultMap id="UserEntityMap" type="cn.hzxh.mybatis.User">
	<result column="id"  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" />
</resultMap>

至此,MyBatis框架已可以满足大多数开发需求。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值