MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO)。
MyBatis是支持定制化SQL、存储过程以及高级映射的一种持久层框架。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis它不完全是一个ORM框架;它需要程序员自己编写部分SQL语句。MyBatis可以通过xml或者注解的方式灵活的配置要运行的SQL语句,并将Java对象和SQL语句映射生成最终的执行的SQL,最后将SQL执行的结果在映射生成Java对象。
MyBatis与Hibernate对比
MyBatis提供一种“半自动化”的ORM实现。这里的“半自动化”,是相对Hibernate等提供了全面的数据库封装机制的“全自动化”ORM实现而言,“全自动”ORM实现了POJO和数据库表之间的映射,以及 SQL 的自动生成和执行。而mybatis的着力点,则在于POJO与SQL之间的映射关系。
无论是用Hibernate还是MyBatis,都可以发现他们有一个共同点:
1. 从配置文件(通常是XML配置文件中)得到 sessionfactory.
2. 由sessionfactory 产生 session
3. 在session 中完成对数据的增删改查和事务提交等.
4. 在用完之后关闭session 。
5. 在java 对象和 数据库之间有做mapping 的配置文件,也通常是xml 文件。
Mybatis与Hibernate区别
- hibernate:它是一个标准的orm框架,比较重量级,学习成本高。
优点:高度封装,使用起来不用写sql,开发的时候,会减低开发周期.
缺点:sql语句无法优化
应用场景:oa(办公自动化系统), erp(企业的流程系统)等,还有一些政府项目,
总的来说,在用于量不大,并发量小的时候使用.
- mybatis:它不是一个orm框架,它是对jdbc的轻量级封装,学习成本低。
优点:学习成本低, sql语句可以优化, 执行效率高,速度快
缺点:编码量较大,会拖慢开发周期
应用场景: 互联网项目,比如电商,P2p等
总的来说是用户量较大,并发高的项目。
- SqlSessionFactoryBuilder
每一个MyBatis的应用程序的入口是SqlSessionFactoryBuilder,它的作用是通过XML配置文件创建Configuration对象(当然也可以在程序中自行创建),然后通过build方法创建SqlSessionFactory对象。没有必要每次访问Mybatis就创建一次SqlSessionFactoryBuilder,通常的做法是创建一个全局的对象就可以了。
- SqlSessionFactory
SqlSessionFactory对象由SqlSessionFactoryBuilder创建。它的主要功能是创建SqlSession对象,和SqlSessionFactoryBuilder对象一样,没有必要每次访问Mybatis就创建一次SqlSessionFactory,通常的做法是创建一个全局的对象就可以了。SqlSessionFactory对象一个必要的属性是Configuration对象,它是保存Mybatis全局配置的一个配置对象,通常由SqlSessionFactoryBuilder从XML配置文件创建。
- SqlSession
SqlSession对象的主要功能是完成一次数据库的访问和结果的映射,它类似于数据库的session概念,由于不是线程安全的,所以SqlSession对象的作用域需限制方法内。SqlSession的默认实现类是DefaultSqlSession,它有两个必须配置的属性:Configuration和Executor。Configuration前文已经描述这里不再多说。
- Executor
Executor对象在创建Configuration对象的时候创建,并且缓存在Configuration对象里。Executor对象的主要功能是调用StatementHandler访问数据库,并将查询结果存入缓存中(如果配置了缓存的话)。
- StatementHandler
StatementHandler是真正访问数据库的地方,并调用ResultSetHandler处理查询结果。
- ResultSetHandler
处理查询结果。
MyBatis配置文件
mybatis-config.xml 是 mybatis 用来建立 sessionFactory 用的,里面主要包含了数据库连接相关东西,还有 java 类所对应的别名,比如 <typeAlias alias="User" type="com.mybatis.model.User"/> 这个别名非常重要,你在 具体的类的映射中,比如userMapper.xml 中 resultType 就是对应这里的,要保持一致。
注意:这些元素在mybatis的xml配置文件中,顺序是不可以颠倒的,否则无法构建SqlSessionFactoryBuilder实例,也就无法构建SqlSessionFactory 工厂实例,更无法构建SqlSession操作数据库命令SQL对象实例。
MyBatis配置文件——mybatis-config.xml:
<configuration> <!-- environments:环境们,MyBatis可以配置多种环境; default:可以指定使用某种环境,可以达到快速切换环境; (说白了,就是default的值与哪个environment的id相同,就走谁的环境配置) environment:配置一个具体的环境信息,必须包含两个标签: transactionManager(事务管理器) 和 dataSource(数据源) dataSource的type有三种取值: UNPOOLED:不使用连接池 POOLED:使用连接池技术 JNDI:使用JNDI技术 --> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <!-- 配置数据库JDBC连接参数 --> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <mappers> <!-- 注册userMapper.xml文件,是包含要映射的类的xml配置文件 --> <mapper resource="com/mybatis/mapping/userMapper.xml"/> </mappers> <!-- 1.MyBatis引入外部配置文件 MyBatis可以使用properties引入外部properties配置文件的内容; resource:引入类路径下的资源 url:引入网络路径或者磁盘路径下的资源 <properties resource="dbconfig.properties"></properties> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> --> <!-- 2.MyBatis的别名用法: 配置别名后在userMapper配置文件中的resultType就写别名就行了,省的写全类名太长 package标签 :为某个包下的所有类批量起别名 name:指定包名(为当下包以及下面所有的后代包的每一个类都起一个默认别名,即小写类名) 如果该包下有个子包,子包里有个类和父包里的类同名,那么就会重名了 解决办法:使用@Alias注解为某个类型指定新的别名,具体方法就是在相应实体类上写:@Alias("emp"),那么这个类的别名就叫emp <typeAliases> <typeAlias type="mypack.bean.Employee" alias="emp"/> <package name="mypack.bean"/> </typeAliases> --> </configuration>
POJO映射文件——userMapper.xml
userMapper.xml映射文件中定义了操作数据库的sql,每个sql是一个statement,映射文件是mybatis的核心。
- namespace:名称空间,作用就是对sql进行分类化管理,配置文件方式编程可以随意起名,接口式编程则要写接口的全类名。
<mapper namespace="com.mybatis.mapping.userMapper"> <select></select> <insert></insert> ...... </mapper>
- 映射查询方法
<!-- id:标识映射文件中的sql。 resultType:指定sql输出结果所映射的java对象类型,映射类的全类名。select指定resultType表示将单条记录映射成java对象。 使用resultType进行输出映射,只有查询出来的列名和POJO中的属性名一致,该列才映射成功。 parameterType:指定输入参数的类型。 #{id}:其中的id表示接收输入的参数,数据名称就是id --> <select id="getEmpById" parameterType="java.lang.String" resultType="com.mybatis.bean.Employee"> <!-- 当数据库字段与实体类字段名称不对应时候可以通过起别名的方式解决 --> select id,last_name lastName,email,gender from tbl_employee where id = #{id,jdbcType=VARCHAR} </select>
JdbcType类型的作用:
在Mybatis明文建议在映射字段数据时需要将JdbcType属性加上,这样相对来说是比较安全的。
插入的时候mybatis不知道具体转换成什么jdbcType类型,通常会使用一个默认设置,虽然默认配置一般情况下不会出错,但是遇到个别情况还是会有问题的。Mybatis经常出现的:无效的列类型:错误,就是因为没有设置JdbcType造成的。
/** 运行流程: ①根据全局配置文件得到SQLSessionFactory ②使用sqlSession工厂,获取到sqlSession对象使用它来执行增删改查 ③使用sql的唯一标识来告诉MyBatis执行哪个sql,sql都是保存在sql映射文件中的 */ public void testSelect() { String resource = "mybatis-config.xml"; InputStream input = Resources.getResourceAsStream(resource); // 获取SQLSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(input); // 获取SqlSession实例,能直接执行已经映射的sql语句 SqlSession sqlSession = sqlSessionFactory.openSession(); //sql的唯一标识,为了防止重复可以用namespace加上id,执行sql要用的参数 Employee emp = sqlSession.selectOne("com.mybatis.mapping.userMapper.getEmpById",1); //提交事务 //sqlSession.commit(); System.out.println(emp); sqlSession.close(); }
- 映射增加方法(需要设置主键生成方式)
<insert id="addEmp" parameterType="com.mybatis.bean.Employee"> insert into tbl_employee(last_name,email,gender) values(#{lastName},#{email},#{gender}) </insert>
Employee emp = new Employee(); emp.setUserName("xxx"); emp.setSex("1"); emp.setAddress("长春"); sqlSession.insert("com.mybatis.mapping.userMapper.addEmp", emp);
- 映射修改方法
<!-- parameterType指定employee对象,包括id和更新信息,注意:id必须存在 --> <update id="updateEmp" parameterType="java.lang.Integer"> update tbl_employee set last_name = #{lastName},email = #{email},gender = #{gender} where id = #{id} </update>
Employee emp = new Employee(); emp.setId(41); //必须设置id emp.setUserName("xxx"); emp.setSex("1"); emp.setAddress("长春"); sqlSession.update("com.mybatis.mapping.userMapper.updateEmp", emp);
- 映射删除方法
<delete id="deleteEmpById" parameterType="java.lang.Integer"> delete from tbl_employee where id = #{id,jdbcType=VARCHAR} </delete>
sqlSession.delete("com.mybatis.mapping.userMapper.deleteEmpById",1);
- 主键生成方式的设置
1. MySQL自增主键值的生成与获取(根据selectkey获取)
<insert id="addEmp" parameterType="mypack.bean.Employee"> <!--将插入数据的主键返回到Employee对象中 SELECT LAST_INSERT_ID():得到刚insert进去记录的主键值,只适用于自增主键。 keyProperty:将查询到的主键设置到parameterType指定的对象的哪个属性。 order:SELECT LAST_INSERT_ID()执行顺序,这个例子表示在insert语句之后执行select。 resultType:指定SELECT LAST_INSERT_ID()的结果类型,必填。 运行顺序: 先运行sql,再查询selectKey将查出的id值封装给javaBean的id属性 --> <selectKey keyProperty=“id" order=“AFTER" resultType=”java.lang.Integer"> SELECT LAST_INSERT_ID() </selectKey> insert into tbl_employee(last_name,email,gender) values(#{lastName},#{email},#{gender}) </insert>
sqlSession.insert(“com.mybatis.mapping.userMapper.addEmp",user); System.out.println(user.getId());
2.MySQL自增主键生成与获取(修改mybatis xml)
useGeneratedKeys="true" :设置是否使用JDBC的getGenereatedKeys方法获取主键并赋值到keyProperty设置的领域模型属性中。(适用于mysql、sqlserver数据库,oracle不能使用,使用selectkey子节点做)
<!-- useGeneratedKeys="true":使用自增主键获取主键值策略 KeyProperty:指定对应的主键属性,也就是MyBatis获取到主键后,将这个值封装给javaBean的哪个属性 --> <insert id="addEmp" parameterType="mypack.bean.Employee" useGeneratedKeys="true" keyProperty="id"> insert into tbl_employee(last_name,email,gender) values(#{lastName},#{email},#{gender}) </insert>
3. MySQL 主键生成( 使用MySQL的uuid()生成主键 )
<!-- 运行顺序: 首先通过uuid得到主键id,然后将主键设置到employee对象的id属性中 然后在insert执行时,从employee对象中去除id属性值 --> <insert id="addEmp" parameterType="com.mybatis.bean.Employee"> <selectKey keyProperty="id" order="BEFORE" resultType="Integer"> <!-- 编写生成uuid主键的查询语句 --> select uuid() </selectKey> <!-- 插入时的主键是从uuid中拿到的,此处获取的id正是从uuid中获取 --> insert into employees(EMPLOYEE_ID,LAST_NAME,EMAIL)values(#{id},#{lastName},#{email}); </insert>
4. Oracle 主键生成(Oracle不支持自增,Oracle使用序列来模拟自增;每次插入的数据的主键是从序列中拿到的值)
<!-- keyProperty:查出的主键值封装给javaBean的哪个属性 order="BEFORE":当前SQL语句必须在insert SQL语句之前运行 resultType:查出的数据的返回值类型 运行顺序: 先运行selectKey从序列中查询id,查出id值封装给javaBean的id属性 再运行插入的sql,就可以取出id属性对应的值 --> <insert id="addEmp" parameterType="com.mybatis.bean.Employee"> <selectKey keyProperty="id" order="BEFORE" resultType="Integer"> <!-- 编写查询主键的sql语句 --> select EMPLOYEES_SEQ.nextval from dual </selectKey> <!-- 插入时的主键是从序列中拿到的,此处获取的id正是从序列中获取 --> insert into employees(EMPLOYEE_ID,LAST_NAME,EMAIL)values(#{id},#{lastName},#{email}); </insert>
#{}和${}的区别:
- #{}:表示一个占位符,以预编译的形式,将参数设置到sql语句中,PrepareStatement,防止sql注入。mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
- ${}:把 ${ } 的内容直接替换成变量的值,会引起sql注入,有安全问题。
#{}
使用#{}意味着使用的预编译的语句,即在使用jdbc时的preparedStatement,sql语句中如果存在参数则会使用?作占位符,我们知道这种方式可以防止sql注入,并且在使用#{}时形成的sql语句,已经带有引号,例,select * from table1 where id=#{id} 在调用这个语句时我们可以通过后台看到打印出的sql为:select * from table1 where id='2' 加入传的值为2.也就是说在组成sql语句的时候把参数默认为字符串。
${}
使用${}时的sql不会当做字符串处理,是什么就是什么,如上边的语句:select * from table1 where id=${id} 在调用这个语句时控制台打印的为:select * from table1 where id=2 ,假设传的参数值为2
从上边的介绍可以看出这两种方式的区别,我们最好是能用#{}则用它,因为它可以防止sql注入,且是预编译的,在需要原样输出时才使用${},如,
select * from ${tableName} order by ${id} 这里需要传入表名和按照哪个列进行排序 ,加入传入table1、id 则语句为:select * from table1 order by id
如果是使用#{} 则变成了select * from 'table1' order by 'id' 我们知道这样就不对了。
在MyBatis中,我们通过parameterType完成输入映射(指将值映射到sql语句的占位符中,值的类型与dao层响应方法的参数类型一致),通过resultType完成输出映射(从数据库中输出,通过dao层的方法查询到的数据输出到pojo对象中)。 注意输入与输出都是相对于数据库的。
输入映射parameterType
MyBatis的传入参数parameterType类型分两种:
- 基本数据类型:int、string、long、Date
- 复杂数据类型:类(JavaBean、Integer等)和Map
代码
- 传入Integer类型
mapper接口代码:
public User findUserById(Integer id);
xml代码:
<select id="findUserById" parameterType="java.lang.Integer" resultType="User"> select * from user where id = #{id}; </select>
- 传入POJO对象
mapper接口代码:
public int findUserList(User user);
xml代码:
<select id="findUserList" parameterType="User" resultType="java.lang.Integer"> SELECT COUNT(*) FROM USER user <where> <if test="code != null"> and user.CODE = #{code} </if> <if test="id != null"> and user.ID = #{id} </if> <if test="idList !=null "> and user.ID in ( <foreach collection="idList" item="id" index="index" separator=","> #{id} </foreach> ) </if> </where> </select>
查询条件可能是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如查询用户信息的时候,将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。
- 传入List
mapper接口代码:
public List<User> findUserListByIdList(List<Integer> idList);
xml代码:
<select id="findUserListByIdList" parameterType="java.util.ArrayList" resultType="User"> select * from user user <where> user.ID in ( <foreach collection="list" item="id" index="index" separator=","> #{id} </foreach> ) </where> </select>
- 传入数组
mapper接口代码:
public List<User> findUserListByIdList(int[] ids);
xml代码:
<select id="findUserListByIdList" parameterType="java.util.List" resultType="User"> select * from user user <where> user.ID in ( <foreach collection="array" item="id" index="index" separator=","> #{id} </foreach> ) </where> </select>
在使用foreach的时候最关键的也是最容易出错的就是collection属性。
该属性是必须指定的,但是在不同情况 下,该属性的值是不一样的,主要有一下3种情况:
1. 如果传入的是单参数且参数类型是一个List的时候,collection属性值为list
2. 如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array
3. 如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了。
- 传入map
mapper接口代码:
public boolean exists(Map<String, Object> map);
xml代码:
<select id="exists" parameterType="java.util.HashMap" resultType="java.lang.Integer"> SELECT COUNT(*) FROM USER user <where> <if test="code != null"> and user.CODE = #{code} </if> <if test="id != null"> and user.ID = #{id} </if> <if test="idList !=null "> and user.ID in ( <foreach collection="idList" item="id" index="index" separator=","> #{id} </foreach> ) </if> </where> </select>
Map中有list或array时,foreach中的collection必须是具体list或array的变量名。
比如这里MAP含有一个名为idList的list,所以MAP中用idList取值,这点和单独传list或array时不太一样。
输出映射——resultType
- 输出简单类型:查询出来的结果集只有一行且一列,可以使用简单类型进行输出映射。
// 得到总记录数 Integer count();
在UserMapper.xml中配置:
<!-- 查询user表中的记录总数 --> <select id="count" resultType="int"> select count(id) from 'user' </select>
输出简单类型必须查询出来的结果集只有一条记录,最终将第一个字段的值转换为输出类型。
- 输出POJO对象和POJO集合:不管是输出的POJO单个对象还是一个列表(list<pojo>),在mapper.xml中resultType指定的类型是一样的。在mapper.java指定的方法返回值类型不一样。生成的动态代理对象中是根据mapper.java方法的返回值类型确定是调用selectOne还是selectList。
public User findUserById(int id); public List<User> findUserByName(String name);
<select id="findById" parameterType="int" resultType="com.oka.po.User"> select * from person where id = #{id} </select> <select id="findUserByName" parameterType="String" resultType="com.oka.po.User"> select * from person where id = #{id} </select>
我们只要返回值类型定义为返回集合中一个元素的类型即可(集合的泛型)。
注意:
如果查找的字段名与返回值属性名不一致则无法完成映射!所以要保证select字段名与返回值类型属性一致
结果集映射——resultMap
resultType可以指定pojo将查询结果映射为pojo,但需要pojo的属性名和sql查询的列名一致方可映射成功。如果sql查询字段名和pojo的属性名不一致,可以通过resultMap将字段名和属性名作一个对应关系 ,resultMap实质上还需要将查询结果映射到pojo对象中。
resultMap 还可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和list实现一对一查询和一对多查询,或者需要在pojo中添加一些并没有与其对应的sql列名的字段。
<resultMap/>元素有很多子元素:
<constructor> /*用来将查询结果作为参数注入到实例的构造方法中*/ <idArg /> /*标记结果作为 ID*/ <arg /> /*标记结果作为普通参数*/ </constructor> <id/> /*一个ID结果,标记结果作为 ID*/ <result/> /*一个普通结果,JavaBean的普通属性或字段*/ <association> /*关联其他的对象*/ </association> <collection> /*关联其他的对象集合*/ </collection> <discriminator> /*鉴别器,根据结果值进行判断,决定如何映射*/ <case></case> /*结果值的一种情况,将对应一种映射规则*/ </discriminator>
我们修改映射文件中的sql,不使用取别名的方式,把resultType替换为resultMap:
1. 定义resultMap
resultMap最终还是要将结果映射到pojo上。
<!-- 将SELECT id id_ , username username_ FROM Employee 和 Employee类中的属性做一个映射关系。 type:resultMap最终映射的java对象类型,可以使用别名。 id:对resultMap的唯一标识。 --> <resultMap type="com.mybatis.bean.Employee" id="userResultMap"> <!-- id表示查询结果集中的唯一标识 column表示查询出来的列名 property表示pojo中的属性名 --> <id column=”id_" property="id" /> <!-- result表示对普通列名映射定义 --> <result column="username_" property="username" /> <!-- 最终resultMap将column和property做一个映射关系 --> </resultMap>
2.使用resultMap进行输出映射
<!-- 指定定义的resultMap的id,如果这个resultMap在其他的mapper文件,前边需要加namespace --> <select id="findUserByIdResultMap" parameterType="int" resultMap="userResultMap"> SELECT id_ , username_ FROM Employee WHERE id = #{value} </select>
3.测试
Employee emp = userMapper.findUserByIdResultMap(1);
接口编程方式
定义接口:
public interface UserMapper { public Employee getEmpById(Integer id); //查询 public void addEmp(Employee employee); //增加 public void updateEmp(Employee employee); //修改 public void deleteEmpById(Integer id); //删除 }
POJO映射文件——userMapper.xml
<!-- namespace:要写接口的全类名 --> <!-- id:接口中的方法名,要写接口中与之对应的哪个方法名,以实现绑定 --> <mapper namespace="mypack.inter.EmployeeMapper"> <!-- 映射查询方法 --> <select id="getEmpById" resultType="mypack.bean.Employee"> select id,last_name lastName,email,gender from tbl_employee where id = #{id}
..........................剩下的和配置文件编程里写的一样..................................
测试方法:
public void test() { String resource = "mybatis-config.xml"; InputStream input = Resources.getResourceAsStream(resource); // 获取SQLSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(input); // 获取SqlSession实例,能直接执行已经映射的sql语句 SqlSession openSession = sqlSessionFactory.openSession(); // 获取接口的实现类对象,会为接口自动的创建一个代理对象,去执行增删改查方法 Employee emp = mapper.getEmpById(1); System.out.println(emp); openSession.close(); }
动态SQL
MyBatis的动态sql语句是基于OGNL表达式的。可以方便的在 sql 语句中实现某些逻辑. 总体说来mybatis 动态SQL 语句主要有以下几类:
- if 语句 (简单的条件判断)
<!-- 测试if(判断):携带了哪个字段查询条件就带上哪个字段 --> <select id="getEmpsByConditionIf" resultType="mypack.bean.Employee"> select *from tbl_employee where <if test="id!=null">id = #{id}</if> <!-- test:判断表达式(OGNL) --> <if test="lastName!=null and lastName!=''">and last_name like #{lastName}</if> <if test="email!=null and email.trim()!=''">and email = #{email}</if> <if test ="gender==0 or gender==1">and gender = #{gender}</if> <!-- OGNL会进行字符串与数字的转换判断 --> </select>
- choose (when,otherwize) ,相当于java 语言中的 switch ,与 jstl 中的choose 很类似
<select id="dynamicChooseTest" parameterType="Blog" resultType="Blog"> select * from t_blog where 1 = 1 <choose> <when test="title != null"> and title = #{title} </when> <when test="content != null"> and content = #{content} </when> <otherwise> and owner = "owner1" </otherwise> </choose> </select>
when元素表示当when中的条件满足的时候就输出其中的内容,跟JAVA中的switch效果差不多的是按照条件的顺序,当when中有条件满足的时候,就会跳出choose,即所有的when和otherwise条件中,只有一个会输出,当所有的我很条件都不满足的时候就输出otherwise中的内容。所以上述语句的意思非常简单, 当title!=null的时候就输出and titlte = #{title},不再往下判断条件,当title为空且content!=null的时候就输出and content = #{content},当所有条件都不满足的时候就输出otherwise中的内容。
- trim (对包含的内容加上 prefix,或者 suffix 等,前缀,后缀)
<!-- prefix:前缀,trim标签体中是整个字符串拼串后的结果,prefix就是给整个字符串加个前缀 prefixOverrides:前缀覆盖,去掉整个字符串前面指定的字符 suffix:后缀,给整个字符串加个后缀 suffixOverrides:后缀覆盖,去掉整个字符串后面指定的字符 --> <select id="getEmpsByConditionTrim" resultType="mypack.bean.Employee"> select *from tbl_employee <trim prefix="where" suffixOverrides="and | or"> <if test="id!=null">id = #{id} and</if> <if test="lastName!=null and lastName!=""">last_name like #{lastName} or</if> <if test="email!=null and email.trim()!=""">email = #{email} and</if> <if test ="gender==0 or gender==1">gender = #{gender}</if> </trim> </select>
trim元素的主要功能是可以在自己包含的内容前加上某些前缀,也可以在其后加上某些后缀,与之对应的属性是prefix和suffix;可以把包含内容的首部某些内容覆盖,即忽略,也可以把尾部的某些内容覆盖,对应的属性是prefixOverrides和suffixOverrides;正因为trim有这样的功能,所以我们也可以非常简单的利用trim来代替where元素的功能 。如上面,可以在查询语句前加上where并去掉末尾多余的and或者or,以保证该语句能够在任何情况下都可以正常执行。
- where (主要是用来简化sql语句中where条件判断的,能智能的处理 and or ,不必担心多余导致语法错误)
<select id="dynamicWhereTest" parameterType="Blog" resultType="Blog"> select * from t_blog <where> <if test="title != null"> title = #{title} </if> <if test="content != null"> and content = #{content} </if> <if test="owner != null"> and owner = #{owner} </if> </where> </select>
where元素的作用是会在写入where元素的地方输出一个where,另外一个好处是你不需要考虑where元素里面的条件输出是什么样子的,MyBatis会智能的帮你处理,如果所有的条件都不满足那么MyBatis就会查出所有的记录,如果输出后是and 开头的,MyBatis会把第一个and忽略,当然如果是or开头的,MyBatis也会把它忽略;此外,在where元素中你不需要考虑空格的问题,MyBatis会智能的帮你加上。像上述例子中,如果title=null, 而content != null,那么输出的整个语句会是select * from t_blog where content = #{content},而不是select * from t_blog where and content = #{content},因为MyBatis会智能的把首个and 或 or 给忽略。
- set (主要用于更新时)
<update id="dynamicSetTest" parameterType="Blog"> update t_blog <set> <if test="title != null"> title = #{title}, </if> <if test="content != null"> content = #{content}, </if> <if test="owner != null"> owner = #{owner} </if> </set> where id = #{id} </update>
set元素主要是用在更新操作的时候,它的主要功能和where元素其实是差不多的,主要是在包含的语句前输出一个set,然后如果包含的语句是以逗号结束的话将会把该逗号忽略,如果set包含的内容为空的话则会出错。有了set元素我们就可以动态的更新那些修改了的字段
- foreach (在实现 mybatis in 语句查询时特别有用)
<!-- collection:指定要遍历的集合 item:将遍历出的元素赋值给指定的变量 separator:每个元素之间的分隔符 open:遍历出所有结果拼接一个开始的字符 close:遍历出所有结果拼接一个结束的字符 index:索引。遍历list的时候是index就是索引,item就是当前值 遍历map的时候index就是map的key,item就是map的值 --> <select id="getEmpsByConditionForeach" resultType="mypack.bean.Employee"> select * from tbl_employee where id in<!-- 查询出id为1和2和3的人员 --> <foreach collection="ids" item="item_id" separator="," open="(" close=")">#{item_id}</foreach> </select>
foreach元素的属性主要有item,index,collection,open,separator,close。
- item:集合中元素迭代时的别名,该参数为必选。
- index:在list和数组中,index是元素的序号,在map中,index是元素的key,该参数可选
- open:foreach代码的开始符号,一般是(和close=")"合用。常用在in(),values()时。该参数可选
- separator:元素之间的分隔符,例如在in()的时候,separator=","会自动在元素中间用“,“隔开,避免手动输入逗号导致sql错误,如in(1,2,)这样。该参数可选。
- close: foreach代码的关闭符号,一般是)和open="("合用。常用在in(),values()时。该参数可选。
- collection: 要做foreach的对象,作为入参时,List对象默认用"list"代替作为键,数组对象有"array"代替作为键,Map对象没有默认的键。当然在作为入参时可以使用@Param("keyName")来设置键,设置keyName后,list,array将会失效。 除了入参这种情况外,还有一种作为参数对象的某个字段的时候。举个例子:如果User有属性List ids。入参是User对象,那么这个collection = "ids".如果User有属性Ids ids;其中Ids是个对象,Ids有个属性List id;入参是User对象,那么collection = "ids.id"
在使用foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的,但是在不同情况 下,该属性的值是不一样的,主要有一下3种情况:
1. 如果传入的是单参数且参数类型是一个List的时候,collection属性值为list
<select id="countByUserList" resultType="_int" parameterType="list">
select count(*) from users
<where>
id in
<foreach item="item" collection="list" separator="," open="(" close=")" index="">
#{item.id, jdbcType=NUMERIC}
</foreach>
</where>
</select>
List<User> users = new ArrayList<User>();
int count = mapper.countByUserList(users);
2. 如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array
<select id="dynamicForeach2Test" resultType="Blog">
select * from t_blog where id in
<foreach collection="array" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
int[] ids = new int[] {1,3,6,9};
List blogs = blogMapper.dynamicForeach2Test(ids);
3. 如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了
<select id="dynamicForeach3Test" resultType="Blog">
select * from t_blog where title like "%"#{title}"%" and id in
<foreach collection="ids" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
final List ids = new ArrayList();
ids.add(1);
ids.add(2);
ids.add(3);
Map params = new HashMap();
params.put("ids", ids);
params.put("title", "中国");
List blogs = blogMapper.dynamicForeach3Test(params);
sql标签与include标签
<sql >标签可以用来定义可重用的SQL 代码片段,可以通过<include >标签包含在其他语句中。
sql 标签
中 id 属性对应 include 标签
中的 BaseConditionSql 属性。通过include标签将 sql 片段
和原 sql 片段进行拼接成一个完成的 sql 语句进行执行。
<select id="selectbyId" resultType="com.heiketu.pojo.Users"> select <include refid="BaseConditionSql"/> from usrs where id = #{id} </select> <!-- 条件查询 SQL --> <sql id="BaseConditionSql"> <if test="username!= null and username!= '-1'"> AND user_name = #{username,jdbcType=VARCHAR} </if> <if test="remark != null and remark != -1"> AND state = #{remark ,jdbcType=VARCHAR} </if> <if test="phone!= null and phone!= '' "> AND phone #{phone,jdbcType=VARCHAR} </if> </sql>
关联查询
一对多关联查询:
通过phone id查询电话信息,并关联查询拥有该电话的Person信息列表,一种方式是先查询tbl_phone表,获取到phone id,再通过phone id从tbl_person表中查询Person信息列表,这种方式要和数据库交互两次,发出两次sql语句,但是数据库返回的数据没有冗余;另一种方式是直接关联查询,只与数据库交互一次,只发出一次sql语句,但是数据库返回数据存在主表信息冗余,冗余数据为主表内容,冗余次数为从表中满足条件的记录数。
因此,应该选择哪一种方式更合理,需要根据具体的业务场景来选择,没有绝对的正确,只有相对的适合。
第一种方式:
此种方式需要查询数据库两次,发出两次sql语句,一次是查询tbl_phone表,一次是查询tbl_person表,但是数据库返回的信息没有冗余。
<mapper namespace="cn.wxy.dao.PersonDao"> <resultMap type="Phone" id="selectPhomap"> <!-- collection标签来解决一对多的关联查询,ofType属性指定集合中元素的对象类型。 --> <collection property="persons" ofType="Person" select="selectPer" column="id"> <!-- 如果要返回整个Person,可以省略下面的字段 --> <id property="id" column="id"/> <result property="username" column="username"/> <result property="password" column="password"/> <result property="gender" column="gender"/> <result property="age" column="age"/> <result property="phoneID" column="phoneID"/> </collection> </resultMap> <select id="select" parameterType="int" resultMap="selectPhomap"> select * from tbl_phone where id=#{id} </select> <select id="selectPer" parameterType="int" resultType="Person"> select * from tbl_person where phoneID=#{phoneID} </select> </mapper>
上面的执行过程:
- 在调用mapper接口中的select方法,先走语句“select * from tbl_phone where id=#{id}”,从数据库中拿到返回数据之后,开始做返回值映射。
- 此时的返回值映射对象是一个resultMap(id:selectPhomap),该resultMap实际上是一个Phone实例,因此在开始做映射的时候,因为属性名和数据库返回值一致完成映射。
- 但是到了persons属性的时候,发现他是一个collection集合对象,里面存放的是Person实例,看collection标签的属性,他需要关联一个查询操作select="selectPer"获取其数据,即通过select * from tbl_person where phoneID=#{phoneID}获取collection中的数据,select="selectPer"操作需要传入数据,传入的数据column="id"即是第一次查询中返回的phone 的 id。
- 最后一步,就是将第二次查询出来的数据映射到collection中。这样就返回了一个复杂的 selectPhomap 对象。
总结:
- 将查询操作拆成两步来完成;
- 第二次查询操作传入的数据通过collection的属性column来传入,此时column的value需要和第一次查询中的返回值做映射,映射的规则是数据库返回值的name和属性name相等;
第二种方式:
此种方式只需要查询数据库一次,发出一次sql语句,但是数据库返回值存在冗余,冗余内容为phone的信息,冗余次数是Person中使用统一电话的人的个数。mapper.xml配置文件内容如下。
<resultMap type="Phone" id="selectPhomap"> <id property="id" column="phoID"/> <result property="phoneNum" column="phoneNum"/> <collection property="persons" ofType="Person"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="password" column="password"/> <result property="gender" column="gender"/> <result property="age" column="age"/> <result property="phoneID" column="phoneID"/> </collection> </resultMap> <select id="select" parameterType="int" resultMap="selectPhomap"> select pho.id phoID, pho.phoneNum, per.* from tbl_phone pho, tbl_person per where pho.id=#{id} and pho.id=per.phoneID </select>
多对一关联查询:
<resultMap id="productBean" type="Product"> <id column="pid" property="id"/> <result column="pname" property="name"/> <!-- 多以一 --> <!-- property: 指的是属性名称, javaType:指的是属性的类型 --> <association property="category" javaType="Category"> <id column="cid" property="id"/> <result column="cname" property="name"/> </association> </resultMap> <select id="productList" resultMap="productBean"> select c.id as 'cid', c.name as 'cname',p.id as 'pid', p.name as 'pname' ,p.price from category c left join product p on c.id = p.cid </select>