MyBatis(一)

MyBatis简介

首先说一下MyBatis是什么?MyBatis就是下图中的鸟,哈哈! mark MyBatis是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设首参数以及获取结果集。MyBatis可以使用简单的XML或注解来配罝和映射原生信息,将接口和Java的POJOs(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录

MyBatis和Hibernate一样,是一个ROM框架(Object Relational Mapping,是对象到关系的映射,是一种解决实体对象与关系型数据库相互匹配的技术,它的实现思想就是将数据库中数据表映射成为对象,对关系型数据以对象的形式进行操作。在软件开发中,对象和关系数据是业务实体的两种表现形式,ORM通过使用描述对象和数据库之间映射的元数据,将对象自动持久化到关系数据库中。

MyBatis相对于Hibernate来说更加轻量级,所以MyBatis其实不具备像Hibernate那样自动建表的功能,但是MyBatis现在对我来说足够用了,现在开始记录一下学习MyBatis过程中遇到的问题或者MyBatis的知识点!

从HelloWorld开始

下面从MyBatis的helloworld说起,如何开始学习MyBatis: 首先是MyBatis的官方网站,这里具有最齐全的MyBatis文档,关键是居然有中文版的!?!?http://www.mybatis.org/mybatis-3/zh/index.html 另外,这个文档有PDF版本的,可以在网上下载到,如果你希望使用离线版的文档去下载这个PDF即可!

这个是MyBatis的maven依赖,如果没学过maven也不要紧,可以参考我的一片博客 《一文读懂Maven》 ,题目有点夸张,大家将就着看吧!如果你不希望使用maven的话直接导入jar包就好了,MyBatis只有一个jar包(但是不要忘记mysql驱动程序的jar包)!

1<dependency>
2  <groupId>org.mybatis</groupId>
3  <artifactId>mybatis</artifactId>
4  <version>x.x.x</version>
5</dependency>

我就演示一下非maven版本的HelloWorld,首先准备一个数据库和表,如图: mark 这是我整个工程的目录结构: mark resource.xml

 1<?xml version="1.0" encoding="UTF-8"?>
 2
 3<!DOCTYPE configuration
 4PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
 5"http://mybatis.org/dtd/mybatis-3-config.dtd">
 6<configuration>
 7	<environments default="development">
 8		<environment id="development">
 9			<transactionManager type="JDBC" />
10			<dataSource type="POOLED">
11				<property name="driver" value="com.mysql.jdbc.Driver" />
12				<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis" />
13				<property name="username" value="root" />
14				<property name="password" value="1234" />
15			</dataSource>
16		</environment>
17	</environments>
18	<mappers>
19		<mapper resource="com/xpu/bean/Employee.xml" />
20	</mappers>
21	
22</configuration>

dataSource 标签配置的数据库连接的基本信息,mappers标签配置的是对象映射关系文件,说一下关于引入dtd约束文档的问题,如果未配置dtd约束文档,那么eclipse是没有提示的,你可以在Window—>Preference–>XML—>XMLCatalog里面配置dtd,点击Add,选择URL,填入dtd的URL,选择FileSystem点击打开下载好的dtd文件即可! mark 接着创建一个JavaBean:

 1package com.xpu.bean;
 2
 3public class Employee {
 4	private Integer id;
 5	private String name;
 6	private char gender;
 7	private String address;
 8	public Employee() {
 9		super();
10	}
11	public Employee(Integer id, String name, char gender, String address) {
12		super();
13		this.id = id;
14		this.name = name;
15		this.gender = gender;
16		this.address = address;
17	}
18	public Integer getId() {...}
19	public void setId(Integer id) {...}
20	public String getName() {...}
21	public void setName(String name) {...}
22	public char getGender() {...}
23	public void setGender(char gender) {...}
24	public String getAddress() {...}
25	public void setAddress(String address) {...}
26	@Override
27	public String toString() {...}
28}

创建关系映射文件employee.xml,注意namespace必须和JavaBean一一对应,sql查询语句不要写错,id是要传入的一个参数:

1<?xml version="1.0" encoding="UTF-8" ?>
2<!DOCTYPE mapper
3PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
4"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
5<mapper namespace="com.xpu.bean.Employee">
6	<select id="selOne" parameterType="java.lang.String" resultType="com.xpu.bean.Employee">
7		select * from employee where id = #{id}
8	</select>
9</mapper>

Test.java

 1package com.xpu.bean;
 2
 3import java.io.IOException;
 4import java.io.Reader;
 5
 6import org.apache.ibatis.io.Resources;
 7import org.apache.ibatis.session.SqlSession;
 8import org.apache.ibatis.session.SqlSessionFactory;
 9import org.apache.ibatis.session.SqlSessionFactoryBuilder;
10
11public class Test {
12	public static void main(String[] args) throws IOException {
13		//通过resource.xml中的配置信息获取一个SqlSessionFactory对象
14		Reader reader = Resources.getResourceAsReader("resource.xml");
15		SqlSessionFactory sqlSessionFty = new SqlSessionFactoryBuilder().build(reader);
16		
17		//开始一个会话
18		SqlSession sqlSession = sqlSessionFty.openSession();
19		
20		//执行查询方法
21		Employee one = sqlSession.selectOne("com.xpu.bean.Employee.selOne", "1");			
22		System.out.println(one);
23		
24		//关闭会话
25		sqlSession.close();
26	}
27}

mark 这便查询到了ID为1的用户,HelloWorld的演示到此结束!

MyBatis的开发模式

1、配置文件

resource.xml

1<mappers>
2	<mapper resource="com/xpu/bean/Employee.xml" />
3</mappers>

Employee.xml

1<mapper namespace="com.xpu.bean.Employee">
2	<select id="selOne" parameterType="java.lang.String" resultType="com.xpu.bean.Employee">
3		select * from employee where id = #{id}
4	</select>
5</mapper>

这样的方式就是我的HelloWorld中所用到的方式,Employee.xml的namespace是我的JavaBean,执行查询的时候需要使用如下方式,selOne是select标签的id,parameterType代表参数的类型,resultType代表返回值的类型,中间是sql查询语句!

1//执行查询方法
2Employee one = sqlSession.selectOne("com.xpu.bean.Employee.selOne", "1");

2、接口

IEmployeeDao.java

1public interface IEmployeeDao {	
2	//查询所有记录
3	@Select("select * from employee")
4	public List<Employee> getAll();
5}

resource.xml

1<mappers>
2	<mapper class="com.xpu.dao.IEmployeeDao"/>
3</mappers>

使用该模式测试:

 1package com.xpu.dao;
 2
 3import java.io.IOException;
 4import java.io.Reader;
 5import java.util.List;
 6
 7import org.apache.ibatis.io.Resources;
 8import org.apache.ibatis.session.SqlSession;
 9import org.apache.ibatis.session.SqlSessionFactory;
10import org.apache.ibatis.session.SqlSessionFactoryBuilder;
11
12import com.xpu.bean.Employee;
13
14public class Test {
15	public static void main(String[] args) throws IOException {
16		Reader reader = Resources.getResourceAsReader("resource.xml");
17		SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
18		
19		//true表示是否自动提交,默认值是false
20		SqlSession session = factory.openSession(true);
21		
22		IEmployeeDao mapper = session.getMapper(IEmployeeDao.class);
23		List<Employee> all = mapper.getAll();
24		for (Employee employee : all) {
25			System.out.println(employee);
26		}
27	}
28}

3、接口+配置文件

这种模式是最常用的模式: Employee.xml:

1<mapper namespace="com.xpu.dao.IEmployeeDao">
2	<select id="getAll" resultType="com.xpu.bean.Employee">
3	    select * from employee
4	</select>
5	
6	<select id="getOne" resultType="com.xpu.bean.Employee">
7	    select * from employee where id = #{id}
8	</select>
9</mapper>

resource.xml

1<mappers>
2	<mapper resource="com/xpu/bean/Employee.xml" />
3</mappers>

IEmployeeDao.java

1package com.xpu.dao;
2public interface IEmployeeDao {
3	
4	public List<Employee> getAll();
5	
6	public Employee getOne(String id);
7}

以上就是MyBatis的三种开发模式,第三种用的比较多,第一种局限性较大,第二种耦合度太强,开发本来的原则就是高内聚、低耦合,使用第二种方式反倒是的耦合度增强不宜使用,所以常用的方式就是第三种开发模式!

下面对需要注意的地方总结一下,参数类型的配置要写全路径名称,避免出错,使用第二种开发模式的时候,需要将接口配置成class,定义返回值类型的时候注意,虽然getAll返回的是一个List集合,但是集合中装的还是对象,所以我们在配置返回值的时候还是需要配置成Employee类:

1<mapper class="com.xpu.dao.IEmployeeDao"/>

最后说一个比较重要的问题,为什么我们通过IEmployeeDao.class对象得到的mapper对象是一个实例呢?我们并没有实现接口呀?没有实现接口的话为什么可以拿到他的实现类对象呢?

这是其实是个代理对象,是通过动态代理来实现的,具体参考Proxy类,看看如何通过动态代理生成代理对象,具体可以参考 《Mybatis是如何通过mapper接口生成代理对象的》

MyBatis的CURD

MyBatis最核心的作用还是增删改查嘛,接下来我通过第三种开发模式总结一下MyBatis的CURD,看看Employee.xml中的SQL语句:

 1<?xml version="1.0" encoding="UTF-8" ?>
 2<!DOCTYPE mapper
 3PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 4"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 5<mapper namespace="com.xpu.dao.EmployeeDao">
 6    
 7    <select id="selAll" resultType="com.xpu.bean.Employee">
 8        select * from employee
 9    </select>
10    
11	<select id="selOne" parameterType="java.lang.String" resultType="com.xpu.bean.Employee">
12	    select * from employee where id = #{id}
13	</select>
14	
15	<insert id="insertEmp" parameterType="com.xpu.bean.Employee">
16	    insert into employee values(#{id}, #{name}, #{gender}, #{address})
17	</insert>
18	
19	<update id="updateEmp" parameterType="com.xpu.bean.Employee">
20	    update employee set name = #{name}, address = #{address} where id = #{id}
21	</update>	
22	
23	<delete id="daleteEmp">
24	    delete from employee where id = #{id}
25	</delete>
26</mapper>

IEmployeeDao.java

 1public interface EmployeeDao {
 2	//查询所有
 3	public List<Employee> selAll();
 4	
 5	//查询单一
 6	public Employee selOne(String id);
 7	
 8	//新增
 9	public void insertEmp(Employee emp);
10	
11	//修改
12	public void updateEmp(Employee emp);
13	
14	//删除
15	public void daleteEmp(String id);
16}

测试代码:

 1public class Demo {
 2	public static void main(String[] args) throws IOException{
 3		Reader reader = Resources.getResourceAsReader("resource.xml");
 4		SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
 5		SqlSession session = sessionFactory.openSession();
 6		
 7		IEmployeeDao mapper = session.getMapper(IEmployeeDao.class);
 8		mapper.insertEmp(new Employee(5, "Lucy", '1', "Lucy@163.com"));
 9		mapper.daleteEmp("4");
10		mapper.updateEmp(new Employee(5, "Lucy", '1', "Lucy@qq.com"));
11		System.out.println(mapper.selOne("2"));
12		session.commit();
13		session.close();
14	}
15}

MyBatis的传参

1、出错演示

好了,增删查改的基本功能已经演示完毕,但是假设我们需要做一个多条件查询的方法,则需要这样写(一下代码均为省略代码,只贴上了重要的代码,其他的代码均在CURD的演示中): Employee.xml

1<select id="queryList" resultType="com.xpu.bean.Employee">
2	    select * from employee where name like '#{name}%' and gender = #{gender}
3</select>

IEmployeeDao.java

1//多条件查询
2public List<Employee> queryList(String name, char gender);

多条件查询测试:

1public void test() {
2		IEmployeeDao mapper = session.getMapper(IEmployeeDao.class);
3		List<Employee> list = mapper.queryList("t", '1');
4		for (Employee employee : list) {
5			System.out.println(employee);
6		}
7}

但是却报错了,注意红线标记的地方,我们需要修改参数的传递方式: mark

2、顺序传参法

其实对于这种多参数的情况,应该这样写Employee.xml,这叫做顺序传参法:

1<select id="queryList" resultType="com.xpu.bean.Employee">
2	    select * from employee where name like '${param1}%' and gender = #{param2}
3	    select * from employee where name = #{0} and gender = #{1}
4</select>

#{ }里面的数字代表你传入参数的顺序,SQL语句的语义表达不直观,且一旦顺序调整容易出错,所以不建议使用!

3、注解传参法

但是这样写的可读性太差,可以使用注解的方式:

1<select id="queryList" resultType="com.xpu.bean.Employee">
2	select * from employee where name like '${name}%' and gender = #{gender}
3</select>
1//多条件查询
2public List<Employee> queryList(@Param("name") String name, @Param("gender") char gender);	

这样可读性强,而且不会因为参数的顺序问题导致程序异常!

4、Map传参法

Employee.xml

1<select id="queryList" resultType="com.xpu.bean.Employee">
2	select * from employee where name like '${name}%' and gender = #{gender}
3<select>

IEmployeeDao.java

1public List<Employee> queryList(Map map);

测试代码:

1public void test() {
2		IEmployeeDao mapper = session.getMapper(IEmployeeDao.class);
3		Map map = new HashMap();
4		map.put("name", "t");
5		map.put("gender", '1');
6		List<Employee> list = mapper.queryList(map);
7}

5、JavaBean传参法

这个在MyBatis的CURD中已经演示过了,resultType="com.xpu.bean.Employee" ,通过#{ 对象属性 } 便可以传参,在此不再赘述!

MyBatis动态SQL

MyBatis 的强大特性之一便是它的动态 SQL。你可以在这个网站学习动态SQL 《动态 SQL》 ,如果你有使用 JDBC 或其他类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句有多么痛苦。拼接的时候要确保不能忘了必要的空格,还要注意省掉列名列表最后的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。

if

就是简单的条件判断,利用if语句我们可以实现某些简单的条件选择

1<!-- 动态SQL -->
2	<select id="queryByIf" resultType="com.xpu.bean.Employee">
3		select * from employee where 1=1
4		<if test="name != null">
5			and name like '${name}%'
6		</if>
7	</select>

但是在上面的例子中,如果name为空的话,where后面就什么都没有,所以加上1=1完全是为了保证语法的正确性,这个问题将在下面解决!

where+if

where标签知道如果它包含的标签中有返回值的话,它就插入一个where。此外,如果标签返回的内容是以AND 或 OR开头的,则它会剔除掉。

 1<select id="queryByWhere" resultType="com.xpu.bean.Employee">
 2		select * from employee
 3		<where>
 4			<if test="name != null">
 5				name like '${name}%'
 6			</if>
 7			<if test="address != null">
 8				and address = #{address}
 9			</if>
10		</where>
11</select>

上述例子中,如果name不为空,address不为空,则SQL语句是select * from employee where name like '${name}%' and address = #{address} ,即使name为空,address前面的and也会被去掉,同样是合法的SQL语句,如果都为空,那就变成了查询所有记录,同样也是合法的SQL语句!

trim

trim标签是可以让我们自己去实现一个where标签,或者实现更多的功能,下面用trim标签去实现一个where标签:

 1<select id="queryByTrim" resultType="com.xpu.bean.Employee">
 2		select * from employee
 3		<trim prefix="where" prefixOverrides="AND | OR">
 4			<if test="name != null">
 5				name like '${name}%'
 6			</if>
 7			<if test="address != null">
 8				and address = #{address}
 9			</if>
10		</trim>
11	</select>

这个trim标签其实就是一个可以通过自定义属性去完成一些功能的标签,prefix="where" 就是代表如果标签中有内容则加上where,如果where后面直接遇到AND或者OR这样的标签就会将其剔除!

set

 1<update id="updateEmployee" parameterType="com.xpu.bean.Employee">
 2        update user 
 3        <set>
 4            <if test="name!=null">
 5                    name=#{name}
 6            </if>
 7        </set>
 8        <where> 
 9            <if test="address!=null">
10                    address=#{address}
11            </if>
12        </where>
13</update>

set标签代替了sql中set关键字,set标签可以自动去除sql中的多余的',',这个和where标签是一样的道理,同样的我们可以使用trim来实现同样的功能:

 1	<update id="updateEmployee" parameterType="com.xpu.bean.Employee">
 2        update user 
 3        <trim prefix="set" prefixOverrides=",">
 4            <if test="name!=null">
 5                    name=#{name}
 6            </if>
 7        </trim>
 8        <where> 
 9            <if test="address!=null">
10                    address=#{address}
11            </if>
12        </where>
13	</update>

choose

chose标签类似于流程控制中switch case default语句,配合when、otherwise标签使用:

 1<select id="queryByChoose" resultType="com.xpu.bean.Employee">
 2		select * from employee where 1=1
 3		<choose>
 4			<when test="name != null">
 5				and name like '${name}%'
 6			</when>
 7			<when test="address != null">
 8				and address = ${address}
 9			</when>
10			<otherwise>
11				order by name
12			</otherwise>
13		</choose>
14	</select>

foreach

foreach标签可迭代任何对象(如列表、集合等)和任何的字典或者数组对象传递给foreach作为集合参数,下面是一个根据ID批量删除的示例:

 1<delete id="deleteByList">
 2		delete from employee where id in
 3		<foreach collection="list" open="(" separator="," close=")"
 4			item="myitem">
 5			#{myitem}
 6		</foreach>
 7</delete>
 8
 9<delete id="deleteByArray">
10		delete from employee where id in
11		<foreach collection="array" open="(" separator="," close=")"
12			item="myitem">
13			#{myitem}
14		</foreach>
15</delete>
1//批量删除
2public Integer deleteByList(List<String> list);
3
4//批量删除
5public Integer deleteByArray(String[] strs);
 1public void func() throws IOException {
 2		Integer array = mapper.deleteByArray(new String[] {"1", "2"});
 3		System.out.println("影响行数:" + array);
 4		
 5		List<String> list = new ArrayList<>();
 6		list.add("3");
 7		list.add("4");
 8		
 9		Integer deleteByList = mapper.deleteByList(list);
10		System.out.println("影响行数:" + deleteByList);
11		session.commit();
12		session.close();
13	}

动态 SQL 语句的编写往往就是一个拼接的问题,为了保证拼接准确,我们最好首先要写原生的 SQL 语句出来,然后在通过 MyBatis 动态SQL 对照着改,防止出错!