1.Mybatis简介
在软件设计模式中,持久层的目的是将数据存储和业务逻辑分离,使得应用程序的其它部分可以独立于数据存储的方式进行开发和维护。Mybatis是一个基于java的持久层框架,Mybatis使用简单的XML或注解用于配置和原始映射,将接口和Java的POJOS(普通java对象)映射成数据库中的记录,java的持久层有许多框架,例如常见的有Hibernate和Mybatis,Mybatis是一个半自动框架,因为需要mybatis手动配置POJO和SQL的映射关系,而Hibernate是全自动,但是过于复杂,两者各有各的优缺点。
1.1. Mybatis原理
(1)读取Myatis配置文件:mybatis-config.xml为Mybatis的全局配置文件,配置了Mybatis的运行环境等信息,例如数据库连接信息。
(2)加载映射文件:即 SQL 映射文件。该文件中配置了操作数据库的SQL语句,需要在mybatis配置文件当中进行加载,一个配置文件可以加载多个映射文件,每个映射文件对应一张表。
(3)构造会话工厂:通过Mybatis的环境等配置信息创建会话工厂SqlSessionFactory。
(4)有了 SqlSessionFactory
之后,你可以使用它来创建会话(SqlSession
)。会话是执行数据库操作的地方,它提供了执行 SQL 语句和事务管理的方法。
(5)Executor 执行器: MyBatis 层定义了 Executor 接口来操作数据库 它将根据SqlSession 传递的参数动态地生成需要执行的 SQL 吾旬,同时负责查询缓存的维护。
(6)MappedStatement 对象:在 Executor 接口的执行方法中有 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射 SQL 语旬id 参数等信息。
(7)输入参数映射:输入参数类型可以是 Map List 等集合类型, 也可以是基本数据 类型和 POJO 类型,输入参数映射过程类似于 JDBC prepareStatement 象设置参数的过程。
(8)输出结果映射:输出结果类型可以是 Map,List 等集合类型,也可以是基本数据类型和POJO 类型。输出结果映射过程类似于 JDBC 对结果集解析过程。、
例子:文件结构如图:
创建日志文件,在资源目录下创建log4j.properties日志文件
log4j.rootLogger=ERROR, stdout
log4j.logger.com.mybatis=DEBUG
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
创建持久化类,创建一个com.mybatis.po的包,在下面命名一个MyUser的持久类:
package com.mybatis.po;
// Press Shift twice to open the Search Everywhere dialog and type `show whitespaces`,
// then press Enter. You can now see whitespace characters in your code.
public class MyUser {
private Integer uid;
private String uname;
private String usex;
public void setUid(Integer uid) {
this.uid = uid;
}
public Integer getUid() {
return uid;
}
public void setUname(String uname) {
this.uname = uname;
}
public String getUname() {
return uname;
}
public void setUsex(String usex) {
this.usex = usex;
}
public String getUsex() {
return usex;
}
@Override
public String toString() {
return "MyUser{" +
"uid=" + uid +
", uname='" + uname + '\'' +
", usex='" + usex + '\'' +
'}';
}
}
创建映射文件对应持久类:
<?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">
<mapper namespace="com.mybatis.mapper.UserMapper">
<select id="selectUserByid" parameterType="Integer"
resultType="com.mybatis.po.MyUser">
select * from springtest where uid=#{uid}
</select>
<select id="selectAllUser" resultType="com.mybatis.po.MyUser">
select * from springtest
</select>
<insert id="addUser" parameterType="com.mybatis.po.MyUser">
insert into springtest(uid,uname,usex) values(#{uid},#{uname},#{usex})
</insert>
<update id="updateUser" parameterType="com.mybatis.po.MyUser">
update springtest set uname=#{uname},usex=#{usex} where uid=#{uid}
</update>
<delete id="deleteUser" parameterType="Integer">
delete from springtest where uid=#{uid}
</delete>
</mapper>
配置Mybatis-config:
<?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">
<configuration>
<!-- 配置环境 -->
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
<environments default="development">
<environment id="development">
<!-- 使用JDBC事务管理器 -->
<transactionManager type="JDBC"/>
<!-- 数据源配置 -->
<dataSource type="POOLED">
<!-- 数据库驱动 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!-- 数据库URL -->
<property name="url" value="jdbc:mysql://localhost:3306/csdnsql?useSSL=false&serverTimezone=UTC"/>
<!-- 数据库用户名 -->
<property name="username" value="root"/>
<!-- 数据库密码 -->
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 映射器配置 -->
<mappers>
<!-- 假设UserMapper.xml位于项目的com.mybatis.mapper包下 -->
<mapper resource="com/mybatis/mapper/UserMapper.xml"/>
<!-- 根据实际情况添加其他mapper映射 -->
</mappers>
</configuration>
创建测试类:
package com.mybatis.test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
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 com.mybatis.po.MyUser;
/**
* MyBatis测试类
* 用于演示MyBatis的基本CRUD操作
*/
public class Mybatistest {
/**
* 程序入口主方法
* @param args 命令行参数
* @throws IOException 如果读取配置文件失败
*/
public static void main(String [] args) throws IOException {
try {
// 加载MyBatis配置文件
InputStream config= Resources.getResourceAsStream("mybatis-config.xml");
// 基于配置文件构建SqlSessionFactory
SqlSessionFactory ssf=new SqlSessionFactoryBuilder().build(config);
// 打开一个SqlSession
SqlSession ss=ssf.openSession();
// 查询用户
MyUser myUser=ss.selectOne("com.mybatis.mapper.UserMapper.selectUserByid",1);
System.out.println(myUser);
// 新增用户
MyUser addmu=new MyUser();
addmu.setUname("add");
addmu.setUsex("男");
ss.insert("com.mybatis.mapper.UserMapper.addUser",addmu);
// 更新用户
MyUser updatemu= new MyUser();
updatemu.setUid(1);
updatemu.setUname("张三");
updatemu.setUsex("女");
ss.update("com.mybatis.mapper.UserMapper.updateUser",updatemu);
// 删除用户
ss.delete("com.mybatis.mapper.UserMapper.deleteUser",3);
// 查询所有用户
List<MyUser> myUsers=ss.selectList("com.mybatis.mapper.UserMapper.selectAllUser");
for(MyUser myuser: myUsers){
System.out.println(myuser);
}
// 提交事务
ss.commit();
// 关闭SqlSession
ss.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
1.2 Mybatis与Spring的整合
这里和Mybatis原生使用构造映射文件有点不一样,上面的Mybatis映射文件通过命名空间来进行调用,但是这里是和Spring进行整合,基本思路是创建一个接口,编写接口对应的对应文件,同时定义resultType来进行返回语句的映射,通过查询获得的值。例子如下:
package com.dao;
import com.mybatis.po.MyUser;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository("userDao")
@Mapper
public interface UserDao {
public MyUser selectUserById(Integer uid);
public List<MyUser> selectAllUser();
public int addUser(MyUser myUser);
public int upAdapter(MyUser myUser);
public int deleteUser(Integer uid);
}
对应的映射文件为:
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">
<mapper namespace="com.dao.UserDao">
<select id="selectUserById" parameterType="Integer"
resultType="com.mybatis.po.MyUser">
select * from springtest where uid=#{uid}
</select>
<select id="selectAllUser" resultType="com.mybatis.po.MyUser">
select * from springtest
</select>
<insert id="addUser" parameterType="com.mybatis.po.MyUser">
insert into springtest(uid,uname,usex) values(#{uid},#{uname},#{usex})
</insert>
<update id="upAdapter" parameterType="com.mybatis.po.MyUser">
update springtest set uname=#{uname},usex=#{usex} where uid=#{uid}
</update>
<delete id="deleteUser" parameterType="Integer">
delete from springtest where uid=#{uid}
</delete>
</mapper>
数据库的配置就配置在applicatio.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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 指定需要扫描的包(包括子包),使注解生效 -->
<context:component-scan base-package="com.dao"/>
<context:component-scan base-package="com.controller"/>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/csdnsql?characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="maxTotal" value="30"/>
<property name="maxIdle" value="10"/>
<property name="initialSize" value="5"/>
</bean>
<!-- 添加事务支持 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 开启事务注解-->
<tx:annotation-driven transaction-manager="txManager" />
<!-- 配置MyBatis工厂,同时指定数据源,并与MyBatis完美整合 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- configLocation的属性值为MyBatis的核心配置文件 -->
<property name="configLocation" value="classpath:com/mybatis/mybatis-config.xml"/>
</bean>
<!--Mapper代理开发,使用Spring自动扫描MyBatis的接口并装配
(Spring将指定包中所有被@Mapper注解标注的接口自动装配为MyBatis的映射接口) -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- mybatis-spring组件的扫描器 -->
<property name="basePackage" value="com.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
</beans>
这里可以看到resultType为返回的类型,parameterType为输入的类型,返回的类型要与自己构建的数据库相对应:
package com.mybatis.po;
public class MyUser {
private Integer uid;
private String uname;
private String usex;
public void setUid(Integer uid) {
this.uid = uid;
}
public Integer getUid() {
return uid;
}
public void setUname(String uname) {
this.uname = uname;
}
public String getUname() {
return uname;
}
public void setUsex(String usex) {
this.usex = usex;
}
public String getUsex() {
return usex;
}
@Override
public String toString() {
return "MyUser{" +
"uid=" + uid +
", uname='" + uname + '\'' +
", usex='" + usex + '\'' +
'}';
}
}
创建配置文件就相对对简单的多:
<?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">
<configuration>
<mappers>
<!-- 假设UserMapper.xml位于项目的com.mybatis.mapper包下 -->
<mapper resource="com/mybatis/UserMapper.xml"/>
<!-- 根据实际情况添加其他mapper映射 -->
</mappers>
</configuration>
编写控制类:
package com.controller;
import com.dao.UserDao;
import com.mybatis.po.MyUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import java.util.List;
@Controller("useController")
public class UseController {
@Autowired
private UserDao userDao;
public void test(){
MyUser auser=userDao.selectUserById(1);
System.out.println(auser);
System.out.println("==========");
MyUser addmu=new MyUser();
addmu.setUid(2);
addmu.setUname("张勇");
addmu.setUsex("男");
int add=userDao.addUser(addmu);
System.out.println("添加了"+add+"条记录");
System.out.println("==========");
int up=userDao.upAdapter(addmu);
System.out.println("修改了"+up+"条记录");
System.out.println("==========");
int del=userDao.deleteUser(5);
System.out.println("删除了"+del+"条记录");
System.out.println("==========");
List<MyUser> list=userDao.selectAllUser();
for(MyUser mu:list){
System.out.println(mu);
}
}
}
编写测试类:
package com.controller;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestController {
public static void main(String [] args){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
com.controller.UseController uc = (UseController) context.getBean("useController");
uc.test();
}
}
可以看出这里spring和mybatis整合之后更加简洁易读,维护性更高。
2.映射器
映射器是十分重要的概念,由一个接口加一个xml文件组成,也可以由注解完成,SQL映射文件常用配置元素如表所示:
2.1 <select>元素
在 SQL 映射文件中<select>元素用于映射 SQL 的 select 语句,其示例代码如下:
<select id=” selectUserByid” parameterType=” Integer ”
resultType=” com.po.MyUser ”>
select * from user where uid = #{uid}
</select>
示例代码中, id 的值是唯一标识符,它接收一个 Integer 类型的参数,返回 一个MyUser 类型的对象,结果集(resultType)自动映射到 MyUser 属性。<select>元素除了有上述示例代码中的几个j副主以外,还有一些常用的属性
2.2 使用Map接口传递多个参数
在实际查询当中,查询不可能那么简单,实际应用当中可能出现查询条件十分复杂,比如查询不止查询id还要符合的其他条件,接受的可能就是多个参数,那么当传递许多参数的时候paramterType应该是什么类型呢?在mybatis当中允许使用map等接口。例如: 假设数据操作接口中有个实现查询陈姓男性用户信息功能的方法:
public List<MyUser> selectAllUser(Map<String, Object> param) ;
那么映射器当中的文件为:其中Map 是一个键值对应的集合,但是map不能限定其传递参数的类型。
Map<String, Object> map=new HashMap<> ();
map.put (” u_name ”,”陈”);
map.put (” u sex ”,”男”);
List<MyUser> list=userDao.selectAllUser(map);
〈 !一查询陈姓男性用户信息 一〉
<select id=” selectAllUser” resultType=” com.po.MyUser” parameterType=”map”>
select * from user
where uname like concat (’%’,#{ u_ame },’%’)
and usex=#{u_sex}
</select>
2.3 使用Java Bean传递多个参数
如果是多个参数,还有另外一种方法也可以进行查询,使用Bean来传递多个参数,首先创建一个持久类:
public class SeletUserParam{
private String u_name;
private String u_sex;
此处省略setter和getter方法
}
然后编写sql映射文件的代码:
<select id="selectAllUser" resultType="com.mybatis.po.MyUser" parameterType="com.pojo.SeletUserParam">
select * from springtest
where uname like concat('%',#{u_name},'%')
and usex=#{u_sex}
</select>
然后修改一下接口当中的方法:
public List<MyUser> selectAllUser(SeletUserParam param);
然后查询的语句也对应的改变:
SeletUserParam su=new SeletUserParam();
su.setU_name (”陈”);
su.setU_sex (”男”) ;
List<MyUser> list=userDao.selectAllUser(su);
for (MyUser myUser : list) {
System.out.println(myUser);
}
正常情况下,应该是根据对应的持久类,编写相应的接口查询方法,然后在sql映射文件当中写好完善查询方法,写好sql语句。在实际应用当中,无论是选择map还是java bean都要按照不同的情况来看,如果参数比较少的话,选择map,参数比较多的话,选择java bean。
2.4 <insert>语句
<insert>元素用于映射插入语句,Mybatis执行完一条插入语句之后,返回一个整数显示其影响行数,他的属性与<select>元素基本相同,但是拥有几个特殊的属性:
keyProperty :该属性的作用是将插入或更新操作时的返回值赋给 PO 类的某个属性,如果是联合主键,可以将多个值用逗号隔开。
keyColumn :该属性用于设置第几列是主键,当主键列不是表中 的第 1 列时需要设
置。如果是联合主键,可以将多个值用逗号隔开。
useGeneratedKeys : 该属性将使 MyBatis 使用 JDBC 的 getGeneratedKeys()方法获取
由数据库内部产生的主键,例如lMySQL 、SQL Server 等自动递增的宇段,其默认值为 false 。
2.5<update>与<delete>元素
这两个元素和前面的也差不多,也是执行了之后返回一个整数,表示影响的行数。配置实例代码如下:
<update id=” updateUser” parameterType =” com.po.MyUser ” >
update user setuname=#{uname} , usex=#{usex} where uid = #{uid}
</update >
<delete i d=”deleteUser ” parameterType =” Integer” >
delete from user where uid = #{uid}
</delete>
2.5<sql>元素
<sql>元素的作用在于可以定义sql语句的一部分,以方便后面的语句引用他,例如反复使用列名,举个例子:
<sql id="comColumns">id,uname , useχ</sql>
<select id=” selectUser” resultType=”com.po .MyUser” >
select <include refid=” comColumns ” /> from user
</select >
2.6<resultMap> 元素
用于表示结果映射集,是Mybatis中最重要的最强大的元素,用来定义映射规则,级联的更新以及定义类型转化器的优化。
<resultMap id="UserResultMap" type="com.example.entity.User">
<!-- 定义resultMap的唯一标识,用于在SQL映射中引用 -->
<!-- type属性指定这个resultMap映射到的实体类 -->
<constructor>
<!-- 使用构造器注入主键值 -->
<!-- column属性对应数据库表中的列名 -->
<!-- javaType属性指定构造方法参数的Java类型 -->
<idArg column="user_id" javaType="int"/>
</constructor>
<!-- 普通属性映射 -->
<!-- column属性对应数据库表中的列名 -->
<!-- property属性对应实体类中的属性名 -->
<result column="username" property="username"/>
<result column="email" property="email"/>
<!-- 一对一关联映射 -->
<!-- property属性指定了实体类中与被关联实体对应的属性名 -->
<!-- javaType属性指定了被关联的实体类型 -->
<association property="address" javaType="com.example.entity.Address">
<!-- 以下为Address实体的映射配置 -->
<id column="address_id" property="id"/>
<result column="street" property="street"/>
<result column="city" property="city"/>
</association>
<!-- 一对多关联映射 -->
<!-- property属性指定了实体类中集合属性的名称 -->
<!-- ofType属性指定了集合中每个元素的类型 -->
<collection property="orders" ofType="com.example.entity.Order">
<!-- Order实体的映射配置 -->
<id column="order_id" property="id"/>
<result column="order_date" property="orderDate"/>
<result column="amount" property="amount"/>
</collection>
</resultMap>
<resultmap>当中的type表示的pojo类。
3.级联查询
级联关系是一个数据库实体的概念,有3种级联关系,分别是一对一级联,一对多级联以及多对多级联,优点是关联数据表,获取关联数据表十分的方便,但是增加了数据库系统的复杂度,降低了性能。如果a表里的外键引用了b表里的主键,那么a表就是子表,b表为父表。当查询a的数据的时候,b表的数据会跟随者a表的外键一起返回。
3.1.一对一级联查询
创建两张表
按照之前所学的基础,定义两个基本的接口,personDao和idcardDao两个接口,然后定义持久层pojo,最主要的是编写sql映射文件,例子如下,因为父表为idcardDao,子表为personDao,所以主要是定义子表的映射文件:
personDao接口:
package com.dao;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import com.po.Person;
import com.pojo.SelectPersonById;
@Repository("personDao")
@Mapper
public interface PersonDao {
public Person selectPersonById1(Integer id);
public Person selectPersonById2(Integer id);
public SelectPersonById selectPersonById3(Integer id);
}
idcardDao接口:
package com.dao;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import com.po.Idcard;
@Repository("idCardDao")
@Mapper
public interface IdCardDao {
public Idcard selectCodeById(Integer i);
}
配置文件:
<?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">
<!-- 定义Mapper接口的命名空间,对应DAO接口PersonDao -->
<mapper namespace="com.dao.PersonDao">
<!-- 定义结果映射,用于将查询结果映射为Person对象,同时包含关联的Idcard信息 -->
<resultMap id="cardAndPerson1" type="com.mybatis.po.Person">
<!-- 映射Person的id属性 -->
<id property="id" column="id"/>
<!-- 映射Person的name属性 -->
<result property="name" column="name"/>
<!-- 映射Person的age属性 -->
<result property="age" column="age"/>
<!-- 映射关联的Idcard信息,通过idcard_id字段关联,使用select子元素进行进一步查询 -->
<!--一对一级联查询-->
<association property="card" column="idcard_id" javaType="com.mybatis.po.Idcard"
select="com.dao.IdcardDao.selectCodeById"/>
</resultMap>
<!-- 定义查询方法,根据id查询Person信息,使用上文定义的结果映射 -->
<select id="selectPersonById1" parameterType="Integer" resultMap="cardAndPerson1">
select * from person where id=#{id}
</select>
<!-- 另一种结果映射方式,直接在resultMap中定义关联的Idcard的映射 -->
<resultMap id="cardAndPerson2" type="com.mybatis.po.Person">
<!-- 映射Person的id属性 -->
<id property="id" column="id"/>
<!-- 映射Person的name属性 -->
<result property="name" column="name"/>
<!-- 映射Person的age属性 -->
<result property="age" column="age"/>
<!-- 映射关联的Idcard信息 -->
<association property="card" javaType="com.mybatis.po.Idcard">
<!-- 映射Idcard的id属性 -->
<id property="id" column="idcard_id"/>
<!-- 映射Idcard的code属性 -->
<result property="code" column="code"/>
</association>
</resultMap>
<!-- 使用上述结果映射,通过id查询Person信息,同时包含关联的Idcard的code -->
<select id="selectPersonById2" parameterType="Integer" resultMap="cardAndPerson2">
select p.*,ic.code
from person p,idcard ic
where p.idcard_id=ic.id and p.id=#{id}
</select>
<!-- 定义查询方法,查询Person信息,但结果类型为自定义的SelectPersonById类 -->
<select id="selectPersonById3" parameterType="Integer" resultType="com.pojo.SelectPersonById">
select p.*,ic.code
from person p,idcard ic
where p.idcard_id=ic.id and p.id=#{id}
</select>
</mapper>
<association>元素主要是定义级联查询当中映射元素,<cloumn>主要对应数据库表当中的列名,基本的方法如配置文件当中所示,大概思路是定义一个resultmap,然后在里面通过association来进行,第二种方法比较直接,直接定义映射对应的列名,来实现级联查询,第三种方法比较特殊。它是通过创建pojo类,来进行操作的。定义一个包含子类,父类的基本列名的类。通过这样简单粗暴的方式进行转换的。
package com.pojo;
public class SelectPersonById {
private Integer id;
private String name;
private Integer age;
private String code;
public void setId(Integer id){
this.id = id;
}
public Integer getId(){
return id;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setAge(Integer age){
this.age = age;
}
public Integer getAge(){
return age;
}
public void setCode(String code){
this.code = code;
}
public String getCode(){
return code;
}
public String toString(){
return "id:"+id+" name:"+name+" age:"+age+" code:"+code;
}
}
3.2 一多对级联查询
当然一对一是最简单的情况,实际情况还有一对多的情况,比如一个用户,对应多个订单,这种情况下就需要在映射文件当中修改配置。首先先创建一个表,一个用户表和一个订单表,订单表和用户表关联。
订单表:
CREATE TABLE `orders` (
`id` tinyint(2) NOT NULL AUTO_INCREMENT,
`ordersn` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL,
`user_id` tinyint(2) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
CONSTRAINT `user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`uid`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
用户表:
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`uid` tinyint(2) NOT NULL AUTO_INCREMENT,
`uname` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
`usex` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`uid`)
) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
编写接口:
package com.dao;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import com.pojo.MapUser;
import com.pojo.SelectUserOrdersById;
import com.po.MyUser;
import com.pojo.SeletUserParam;
@Repository("userDao")
@Mapper
public interface UserDao {
public MyUser selectUserById(Integer uid);
//public List<MyUser> selectAllUser(Map<String, Object> param);
public List<MyUser> selectAllUser(SeletUserParam param);
public List<MapUser> selectResultMap();
public int addUser(MyUser user);
public int updateUser(MyUser user);
public int deleteUser(Integer uid);
public List<Map<String, Object>> selectAllUserMap();
public MyUser selectUserOrdersById1(Integer uid);
public MyUser selectUserOrdersById2(Integer uid);
public List<SelectUserOrdersById> selectUserOrdersById3(Integer uid);
}
对应的配置文件:
<?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">
<mapper namespace="com.dao.UserDao">
<!-- 根据uid查询一个用户信息 -->
<select id="selectUserById" parameterType="Integer"
resultType="com.po.MyUser">
select * from user where uid = #{uid}
</select>
<!-- 查询陈姓男性用户信息 -->
<select id="selectAllUser" resultType="com.po.MyUser" parameterType="com.pojo.SeletUserParam">
select * from user
where uname like concat('%',#{u_name},'%')
and usex = #{u_sex}
</select>
<!-- 添加一个用户,成功后将主键值回填给uid(po类的属性),#{uname}为com.po.MyUser的属性值-->
<insert id="addUser" parameterType="com.po.MyUser"
keyProperty="uid" useGeneratedKeys="true">
insert into user (uname,usex) values(#{uname},#{usex})
</insert>
<!-- 修改一个用户 -->
<update id="updateUser" parameterType="com.po.MyUser">
update user set uname = #{uname},usex = #{usex} where uid = #{uid}
</update>
<!-- 删除一个用户 -->
<delete id="deleteUser" parameterType="Integer">
delete from user where uid = #{uid}
</delete>
<!-- 使用自定义结果集类型 -->
<resultMap type="com.pojo.MapUser" id="myResult">
<!-- property是com.pojo.MapUser类中的属性-->
<!-- column是查询结果的列名,可以来自不同的表 -->
<id property="m_uid" column="uid"/>
<result property="m_uname" column="uname"/>
<result property="m_usex" column="usex"/>
</resultMap>
<!-- 使用自定义结果集类型查询所有用户 -->
<select id="selectResultMap" resultMap="myResult">
select * from user
</select>
<!-- 查询所有用户信息存到Map中 -->
<select id="selectAllUserMap" resultType="map">
select * from user
</select>
<!-- 一对多 根据uid查询用户及其关联的订单信息:第一种方法(嵌套查询) -->
<resultMap type="com.po.MyUser" id="userAndOrders1">
<id property="uid" column="uid"/>
<result property="uname" column="uname"/>
<result property="usex" column="usex"/>
<!-- 一对多关联查询,ofType表示集合中的元素类型,将uid传递给selectOrdersById-->
<collection property="ordersList" ofType="com.po.Orders" column="uid"
select="com.dao.OrdersDao.selectOrdersById"/>
</resultMap>
<select id="selectUserOrdersById1" parameterType="Integer" resultMap="userAndOrders1">
select * from user where uid = #{id}
</select>
<!-- 一对多 根据uid查询用户及其关联的订单信息:第二种方法(嵌套结果) -->
<resultMap type="com.po.MyUser" id="userAndOrders2">
<id property="uid" column="uid"/>
<result property="uname" column="uname"/>
<result property="usex" column="usex"/>
<!-- 一对多关联查询,ofType表示集合中的元素类型 -->
<collection property="ordersList" ofType="com.po.Orders" >
<id property="id" column="id"/>
<result property="ordersn" column="ordersn"/>
</collection>
</resultMap>
<select id="selectUserOrdersById2" parameterType="Integer" resultMap="userAndOrders2">
select u.*,o.id,o.ordersn from user u, orders o where u.uid = o.user_id and u.uid=#{id}
</select>
<!-- 一对多 根据uid查询用户及其关联的订单信息:第三种方法(使用POJO存储结果) -->
<select id="selectUserOrdersById3" parameterType="Integer" resultType="com.pojo.SelectUserOrdersById">
select u.*,o.id,o.ordersn from user u, orders o where u.uid = o.user_id and u.uid=#{id}
</select>
</mapper>
编写持久类如之前一样,但是需要注意的是要在其中添加一对多的联系,要在user的持久类当中增加orders也就是订单类的集合映射:以及相应的setter方法和getter方法
private List<Orders> ordersList;
public void setOrdersList(List<Orders> ordersList) {
this.ordersList = ordersList;
}
public List<Orders> getOrdersList() {
return ordersList;
}
控制类以及测试类:
package com.controller;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestOneToMore {
public static void main(String[] args) {
ApplicationContext appCon = new ClassPathXmlApplicationContext("applicationContext.xml");
OneToMoreController otm = (OneToMoreController)appCon.getBean("oneToMoreController");
otm.test();
}
}
package com.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import com.dao.UserDao;
import com.po.MyUser;
import com.pojo.SelectUserOrdersById;
@Controller("oneToMoreController")
public class OneToMoreController {
@Autowired
private UserDao userDao;
public void test() {
MyUser auser1 = userDao.selectUserOrdersById1(1);
System.out.println(auser1);
System.out.println("===================================");
MyUser auser2 = userDao.selectUserOrdersById2(1);
System.out.println(auser2);
System.out.println("===================================");
List<SelectUserOrdersById> auser3 = userDao.selectUserOrdersById3(1);
System.out.println(auser3);
System.out.println("===================================");
}
}
测试结果如下:
总结:其实和一对一级联查询一样,也是有三种方法,第三种方式是通过pojo方法进行存储结果集,其次前两种方法都是通过<collection>元素进行返回结果集。然后现在可以发现一个问题,为什么接口并没有实例化,但是却能调用方法呢,这里和前面学到的有关系,通过jdk动态代理其实已经实现了,当你在Spring框架中使用MyBatis时,通常会配置MapperScannerConfigurer或通过@MapperScan注解来指定Mapper接口所在的包。Spring会扫描这些包下的接口,并为每个接口创建一个动态代理对象。MyBatis内部会为每个Mapper接口生成一个动态代理对象。当调用Mapper接口的方法时,实际上是在调用这个动态代理对象。因此,虽然你直接调用的是接口的方法,但实际上背后是MyBatis通过动态代理和SqlSession来执行SQL查询并处理结果,使得你可以以面向对象的方式操作数据库,而无需直接编写JDBC代码。
3.3 多对多级联查询
其实Mybatis当中并没有多对多级联查询,但是可以通过两个一对多级联查询来进行实现。例如,一个订单有多个商品,一个商品有多个订单,那就是多个订单对应多个商品之间的联系,可以创建一个中间表(订单记录表),就可以将多对多转化为两个一对多的情况。订单表前面已经创建,这里创建商品表和订单记录表:
DROP TABLE IF EXISTS `product`;
CREATE TABLE `product` (
`id` tinyint(2) NOT NULL,
`name` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
`price` double DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
DROP TABLE IF EXISTS `orders_detail`;
CREATE TABLE `orders_detail` (
`id` tinyint(2) NOT NULL AUTO_INCREMENT,
`orders_id` tinyint(2) DEFAULT NULL,
`product_id` tinyint(2) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `orders_id` (`orders_id`),
KEY `product_id` (`product_id`),
CONSTRAINT `orders_id` FOREIGN KEY (`orders_id`) REFERENCES `orders` (`id`),
CONSTRAINT `product_id` FOREIGN KEY (`product_id`) REFERENCES `product` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
创建持久类:
package com.po;
import java.util.List;
public class Product {
private Integer id;
private String name;
private Double price;
private List<Orders> orders;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public List<Orders> getOrders() {
return orders;
}
public void setOrders(List<Orders> orders) {
this.orders = orders;
}
@Override
public String toString() {
return "Product [id=" + id + ",name=" + name + ",price=" + price + "]";
}
}
其中要表现出两个一对多当中的一个,就是在这里面的:
private List<Orders> orders;
中间表就不需要创建持久类了,它主要体现在数据库方面的设计,另外一个就是在订单表orders的持久类里面添加另外一个一对多的关系:其中自行补充setter和getter方法
private List<Product> products;
根据订单表建立接口以及映射文件:
package com.dao;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import com.po.Orders;
@Repository("ordersDao")
@Mapper
public interface OrdersDao {
public List<Orders> selectOrdersById(Integer uid);
public List<Orders> selectallOrdersAndProducts();
}
<?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">
<mapper namespace="com.dao.OrdersDao">
<!-- 根据用户uid查询订单信息 -->
<select id="selectOrdersById" parameterType="Integer" resultType="com.po.Orders">
select * from orders where user_id=#{id}
</select>
<!-- 多对多关联 查询所有订单以及每个订单对应的商品信息(嵌套结果) -->
<resultMap type="com.po.Orders" id="allOrdersAndProducts">
<id property="id" column="id"/>
<result property="ordersn" column="ordersn"/>
<!-- 多对多关联 -->
<collection property="products" ofType="com.po.Product">
<id property="id" column="pid"/>
<result property="name" column="name"/>
<result property="price" column="price"/>
</collection>
</resultMap>
<select id="selectallOrdersAndProducts" resultMap="allOrdersAndProducts">
select o.*,p.id as pid,p.name,p.price
from orders o,orders_detail od,product p
where od.orders_id = o.id
and od.product_id = p.id
</select>
</mapper>
测试方法同上,结果如下:
4.动态SQL
开发人员可能需要经常组装sql语句,但是这会非常的麻烦,所以mybatis提供了对sql语句动态组装的功能,<if> 、<choose>、<when> 、<otherwise> 、<trim> 、<where> 、<set>、<foreach>和<bind>等元素。下面将会着重介绍一些元素的基本用法。
4.1 <if>元素
动态sql常做的语句就是有条件的包含where子句的一部分,所以在Mybatis当中是最常用的元素。
<select id="selectUserByIf" resultType="com.po.MyUser" parameterType="com.po.MyUser">
select * from user where 1=1
<if test="uname !=null and uname!=''">
and uname like concat('%',#{uname},'%')
</if>
<if test="usex !=null and usex!=''">
and usex = #{usex}
</if>
</select>
4.2<choose>、<when>、<otherwise>元素
类似于选择元素,从其中选择符合的条件,类似于switch语句。
<select id="selectUserByChoose" resultType="com.po.MyUser" parameterType="com.po.MyUser">
select * from user where 1=1
<choose>
<when test="uname !=null and uname!=''">
and uname like concat('%',#{uname},'%')
</when>
<when test="usex !=null and usex!=''">
and usex = #{usex}
</when>
<otherwise>
and uid > 10
</otherwise>
</choose>
</select>
4.3<trim>、<where>、<set>元素
<trim>可以去除前缀或后缀:可以指定字符串前缀或后缀,在构造SQL时自动去除这些指定的字符;与去除相对,也可以选择保留某些特定的前缀或后缀,通俗易懂一点就是可以加上某些后缀或者前缀,也可以选择性的忽视一些前缀或者后缀,加上的元素为prefix和suffix,忽视的元素为perfixOverrides和suffixOverrides。
<select id="selectUserByTrim" resultType="com.po.MyUser" parameterType="com.po.MyUser">
select * from user
<trim prefix="where" prefixOverrides="and |or">
<if test="uname !=null and uname!=''">
and uname like concat('%',#{uname},'%')
</if>
<if test="usex !=null and usex!=''">
and usex = #{usex}
</if>
</trim>
</select>
4.3.3<where>元素
where元素的一个好处是不用考虑里面的条件是什么样的,不满足的条件的时候会将其全部输出,如果输出的第一个是and它会将其忽视掉,同样or也是如此。
<select id="selectUserByWhere" resultType="com.po.MyUser" parameterType="com.po.MyUser">
select * from user
<where>
<if test="uname !=null and uname!=''">
and uname like concat('%',#{uname},'%')
</if>
<if test="usex !=null and usex!=''">
and usex = #{usex}
</if>
</where>
</select>
4.3.4<set>元素
set语句用在动态更新update当中,可以使用set元素更新列。
<update id="updateUserBySet" parameterType="com.po.MyUser">
update user
<set>
<if test="uname != null">uname=#{uname},</if>
<if test="usex != null">usex=#{usex}</if>
</set>
where uid = #{uid}
</update>
4.4<foreach>元素
这个元素主要用在in的构造条件当中,用于迭代一个集合。它包含的主要属性有:
item:表示集合当中每一个元素迭代的别名。
index:指定一个名字,用于表示迭代到的位置。
open:表示迭代从哪个位置开始。
separator:表示迭代的时候以什么符号为分隔符。
close:表示以什么结束。
<collection>:该属性是必须选的,但是在不同的情况下不一样,如果传入的是一个List,则属性为list。如果传入的是一个array数组,则属性为list。如果传入的是多个,则封装为map,当然单个参数也可以封装为map.其中map里面的key指的是参数名,而属性值是list和array的对象在自己封装的map中的key。
<select id="selectUserByForeach" resultType="com.po.MyUser" parameterType="List">
select * from user where uid in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
4.5 <bind>元素
主要用于模糊查询,用${}拼接字符串无法防止sql注入问题,如果使用字符串拼接函数或者字符串符号,但是不同的数据库连接符号不一样,但是mybatis提供了bind元素,增加了代码的可移植性。