一、mybatis架构
1、mybatis介绍
mybatis本是apache的一个开源项目iBatis,2010年这个项目由apache software迁移到了google code,并且改名为mybatis,实质上对ibatis进行一些改进。目前mybatis在github上托管。
mybatis是持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注SQL本身,不需要花费精力去处理其他东西。
mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。
2、mybatis架构
SqlMapConfig.xml:
SqlMapConfig.xml中配置的内容顺序如下:
properties(属性)
settings(全局配置参数)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境集合属性对象)
environment(环境子属性对象)
transactionManager(事务管理)
dataSource(数据源)
mappers(映射器)
mybatis的全局配置文件,名称不固定,主要配置的是运行环境(数据源、事物),设置全局参数,设置别名
引入数据源外部文件:
<!-- 1、属性定义引入外部数据库文件 -->
<properties resource="db.properties"></properties>
设置全局参数:
<settings></settings>
设置别名:
<!-- 3、设置别名 -->
<typeAliases>
<!-- 单个别名的定义
alias:别名,type:别名映射的类型 你的类的全局路径-->
<typeAlias type="cn.itcast.mybatis.pojo.User" alias="user"/>
<typeAlias type="cn.itcast.mybatis.parameterType.UserQueryVo" alias="userQueryVo"/>
<!-- 指定包路径,自动扫描包下边的pojo,定义别名,别名默认为类名 -->
<!-- <package name="cn.itcast.mybatis.pojo"/> -->
</typeAliases>
配置数据库:(和spring整合后不需要了)
<environments default="development">
<environment id="development">
<!-- 使用jdbc事物管理-->
<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>
引入mapper.xml文件(该文件主要配置的是sql语句):
<mappers>
<mapper resource="mapper/mapper.xml"/>
</mappers>
mapper.xml:
mybatis有两种开发dao的方法:(1)原始开发dao方式(dao接口和dao实现都需要编写)(2)mapper代理方式(只需要写dao接口)
原始开发dao方式
mapper.xml文件编写:
<!-- namespace命名空间,为了对sql语句进行隔离,方便管理-->
<mapper namespace="test">
<!-- 根据用户id查询信息 mapper.xml以statement为单位管理sql语句,封装为mappedStatement对象-->
<!--id:唯一标识一个statement-->
<!--parameterType:输入的参数类型-->
<!--ireturnType:输出的结果类型,单条记录映射的pojo类型-->
<select id="findUserById" resultType="cn.itcast.mybatis.pojo.User" parameterType="int">
select * from user where id = #{id}
</select>
</mapper>
需要编写dao接口和dao的实现类
dao接口:
public interface UserDao {
public User findUserById(int id) throws Exception;
}
dao实现类:
public class UserDaoImpl implements UserDao{
//工厂类
private SqlSessionFactory sqlSessionFactory;
public UserDaoImpl(SqlSessionFactory sqlSessionFactory) {
super();
this.sqlSessionFactory = sqlSessionFactory;
}
public User findUserById(int id) throws Exception{
SqlSession sqlSession = sqlSessionFactory.openSession();
//根据id查询信息
// 如果查询结果返回的是多条记录,使用selectOne();
User user = sqlSession.selectOne("test.findUserById", 1);
sqlSession.close();
return user;
}
}
原始开发dao配置文件创建SqlSessionFactory
private SqlSessionFactory sqlSessionFactory;
@Before
public void init() throws IOException{
// 全局配置文件
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//创建sqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
原始开发dao获取测试
@Test
public void testFindUserById(){
UserDao userDao = new UserDaoImpl(sqlSessionFactory);
User user = userDao.findUserById(1);
System.out.println(user);
}
mapper代理方式:
程序员只需要写dao接口,到接口实现对象由mybatis自动生成代理对象。
原始开发dao方式存在问题:
1、dao的实现类中 存在重复代码,整个mybatis操作的过程代码模块重复(先创建sqlSession、调用sqlsession的方法、关闭sqlsession)
2、dao的实现类中存在硬编码,调用sqlsession方法时将statement的id硬编码。
mapper.xml文件和mapper.java文件(每个方法输入参数只能是一个,想输入多个通过扩展pojo)编写:
1、mapper.xml中的namespace指定为mapper接口的权限定名:将mapper.xml和mapper.java进行关联
<!-- namespace命名必须是指定为mapper接口的全限定名 -->
<mapper namespace="cn.itcast.mybatis.dao2.UserMapper">
2、mapper.xml中的statement的id就是mapper.java中的方法名:
3、mapper.xml中的statement的parameterType和mapper.java中方法输入参数类型一致
4、mapper.xml中的statement的resultType和mapper.java中方法返回值类型一致
<select id="findUserById" resultType="user" parameterType="int">
select * from user where id = #{id}
</select>
public interface UserMapper {
public User findUserById(int id) throws Exception;
5、mapper接口中返回单个对象和集合对象
不管查询记录是单条还是多条,在statement中resultType定义一致,都是单条记录映射的pojo类型。
mapper接口方法返回值,如果返回的单个对象,返回值类型是pojo类型,生成的代理对象内部通过selectOne获取记录,如果返回值类型是集合对象,生成的代理对象内部通过selectList获取记录
public interface UserMapper {
// 调用selectOne获取记录
public User findUserById(int id) throws Exception;
// 调用selectList获取记录
public List<User> findUserByName(String username) throws Exception;
<select id="findUserById" resultType="user" parameterType="int">
select * from user where id = #{id}
</select>
<!-- 根据姓名查询用户信息 -->
<select id="findUserByName" resultType="user" parameterType="String">
select * from user where username = '%${username}%'
</select>
6、返回值问题
调用statement,返回多条记录,mapper.java中方法的返回类型不能是pojo类型,因为是pojo类型,底层会调用selectOne函数查询结果,会报错!!
7、输入参数问题(解决Mapper.java中方法只能输入一个参数的问题)
可以通过扩展pojo(自己定义pojo包装类型,将要传入的参数传入进去。)
public List<User> findUserList(UserQuery userQuery) throws Exception;
public class UserQueryVo {
private User user;
private UserCustom userCustom;
private List<Integer> ids;
public class UserCustom extends User {
}
//测试findUserList
@Test
public void testFindUserList() throws Exception{
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
UserQueryVo userQueryVo = new UserQueryVo();
UserCustom userCustom = new UserCustom();
userCustom.setUsername("小明");
userQuery.setUserCustom(userCustom);
List<User> list = userMapper.findUserList(userQueryVo);
sqlSession.close();
System.out.println(list);
}
mybatis开发小结:
1、编写SqlMapConfig.xml :引入数据库文件,引入mapper.xml文件
2、编写mapper.xml文件:定义statement,封装成MappedStatement对象返回。
3、编程通过配置文件创建SqlSessionFactory
4、通过sqlSessionFactory获取SqlSession
5、通过SqlSession操作数据库
6、关闭SqlSession!!
因为插入记录需要一个主键返回:有两种方式
mapper.xml文件中
通过LAST_INSERT_ID()获取刚插入记录的自增主键值,在insert语句执行后,执行select LAST_INSERT_ID()就可以获取自增主键
通过使用mysql的uuid机制生成主键:使用uuid生成主键的好处是不考虑数据库移植后主键冲突问题。
<!-- 添加用户 -->
<insert id="insertUser" parameterType="cn.itcast.mybatis.pojo.User">
<!-- 通过LAST_INSERT_ID()获取刚插入记录的自增主键值 -->
<!-- selectKey:用于生成主键返回,定义了获取主键的sql-->
<!-- order:设置selectKey中sql执行的顺序,相对于insert来说-->
<!-- 将主键设置到哪个属性-->
<!-- select结果类型-->
<!-- <selectKey keyProperty="id" order="AFTER" resultType="int">
select LAST_INSERT_ID();
</selectKey> -->
<!-- 通过uuid生成主键 -->
<selectKey keyProperty="id" resultType="string" order="BEFORE">
select uuid();
</selectKey>
Insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
</insert>
SqlSessionFactory(会话工厂)
作用:创建SqlSessionFactory
SqlSession:(面向用户的接口,数据库操作方法)
作用:操作数据库
Executor(数据库操作的执行器)
executor是一个接口,有两个实现(默认执行器和缓存执行器)
MappedStatement(mybatis的封装对象)
作用:封装sql语句
在mapper.xml中封装的sql语句返回的就是mappedStatement对象
每一个select就是mappedStatement对象
<!-- 根据id查询用户信息 -->
<!-- id:唯一表示statement -->
<select id="findUserById" resultType="user" parameterType="int">
<!--通过占位符传递参数 -->
select * from user where id = #{id}
</select>
<!-- 根据姓名查询用户信息 -->
<select id="findUserByName" resultType="user" parameterType="String">
<!--通过拼接传递参数-->
select * from user where username = '%${username}%'
</select>
mybatis解决jdbc编程问题
1、数据库创建连接、释放频繁造成系统资源浪费从而影响性能,所以使用数据库连接池
解决:在SqlMapConfig.xml中配置数据连接池,使用连接池管理数据库连接。
2、Sql语句写在代码中造成代码不易维护,实际应用啥情况sql变化的可能较大,sql变动需要改变java代码
解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。
3、向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
解决:Mybatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。
4、对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
解决:Mybatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。
resultMap(返回类型)
resultType与resultMap对比:
resultType:指定输出结果的类型,将sql查询结果映射为java对象。
注意:使用resultType时,sql查询的列名要和resultType指定的pojo的属性名相同,指定相同的属性才可以映射成功,如果sql查询的列名要和resultType指定的pojo的属性名全部不相同,list中无法创建pojo对象。
resultMap:将查询结果映射为java对象
如果查询列名和最终要映射的pojo的属性名不一致,使用resultMap将列名和pojo的属性名做一个对应关系(列名和属性名映射设置)
resultMap
<!-- 列名和属性名映射设置 column:别名 property:user中的属性名称-->
<resultMap id="userListResultMap" type="user">
<!-- 列名
id_,username_,birthday
column:运行sql语句后的列名
property:type指定的哪个属性-->
<id column="id_" property="id"/>
<result column="username_" property="username"/>
<result column="birthday_" property="birthday"/>
</resultMap>
使用resultMap
<!-- 使用resultMap进行映射后的查询 -->
<select id="findUserListResultMap" parameterType="userQueryVo" resultMap="userListResultMap">
select id id_,username username_,birthday birthday_ from user where username like '%${userCustom.username}%'
</select>
动态sql
#{}和${}完成输入参数的属性值获取,通过OGNL获取parameterType指定pojo的属性名。
#{}:占位符号,好处防止sql注入
${}:sql拼接符号
if和where
通过sql片段可以将sql语句抽取出来,单独定义,在其他statement中可以引用sql片段。
<!-- 动态sql -->
<select id="sql" parameterType="userQueryVo" resultType="user">
select * from user where
<where>
<!-- 引用sql片段 -->
<include refid="query_user_where"></include>
</where>
</select>
<!-- 将用户查询条件定义为sql片段,提取出来方便引用 -->
<sql id="query_user_where">
<if test="userCustom!=null">
<if test="userCustom.username != null and userCustom,username != ''">
and username like '%${userCustom.username}%'
</if>
<if test="userCustom.sex != null and userCustom.sex != ''">
and sex = #{userCustom.sex}
</if>
<!-- 还有很多查询条件 -->
</if>
</sql>
<!-- 动态sql -->
<select id="sql" parameterType="userQueryVo" resultType="user">
select * from user where
<where>
<sql id="query_user_where">
<if test="userCustom!=null">
<if test="userCustom.username != null and userCustom,username != ''">
and username like '%${userCustom.username}%'
</if>
<if test="userCustom.sex != null and userCustom.sex != ''">
and sex = #{userCustom.sex}
</if>
<!-- 还有很多查询条件 -->
</if>
</sql>
</where>
</select>
// 这个查询相当于
select * from user where username like '%${userCustom.username}%' and sex = #{userCustom.sex};
foreach
在statement通过foreach遍历parameterType中的集合类型
<!--
collection : 集合的属性 就是pojo类型中的哪个集合属性
open:开始循环的拼接字符串本例是AND id IN(
close:结束循环拼接的串
item:每次循环获取的对象
separator:每两次循环中间拼接的串
-->
<select id="findUserByIdGroup" parameterType="userQueryVo" resultType="User">
select id,username,birthday from user where username like '%小明%'
<foreach collection="ids" open="AND id IN(" close=")" item="id" separator=",">
#{id}
</foreach>
</select>
//相当于
select id,username,birthday from user where username like '%小明%' and id in(id1,id2,id3);
<select id="findUserByIdGroup" parameterType="userQueryVo" resultType="User">
select id,username,birthday from user where username like '%小明%'
<foreach collection="ids" open="and (" colse=")" item="id" separator="or">
id=#{id}
</select>
//相当于
select id,username,birthday from user where username like '%小明%' and (id = 16 or id = 24 or id = 36);
resultMap完成一对一、一对多、多对多查询
当关联多个表进行查询的时候,一般resultType无法完成,多使用resultMap完成
resultType实现一对一查询:先确定主查询表 orders,然后确定关联信息表user,通过外键确定连接
// 查询语句:select orders.*,user.username,user.sex from orders,user where orders.user_id = user.id;
根据查询语句的返回类型确定扩展的pojo类型
public class OrderCustom extends Orders{
//补充用户信息
private String username;
private String sex;
}
mapper.xml中编写:
<select id="findOrderUserList" resultType="OrderCustom">
//查询语句
</select>
mapper.java
public interface OrdersMapperCustom{
public List<OrderCustom> findOrderUserList() throws Exception;
}
使用resultMap实现一对一
resultMap提供一对一关联查询的映射和一对多关联查询映射,一对一映射思路:将关联查询的信息映射到pojo中。
下列查询语句就是讲关联信息的pojo映射到主查询信息pojo中
// 查询语句:select orders.*,user.username,user.sex from orders,user where orders.user_id = user.id;
public class Orders{
private Integer id;
private Integer userId;
private String number;
private Date createtime;
private String note;
//关联用户信息
private User user;
}
mapper.xml
<!-- 一对一查询使用resultMap完成
查询订单关联查询用户信息 -->
<select id="findOrderUserListResultMap" resultMap="ordersUserResultMap">
SELECT orders.*,user.username,user.sex FROM orders,USER
WHERE orders.user_id = user.id;
</select>
resultMap定义:
<!-- 一对一查询resultMap -->
<resultMap type="orders" id="ordersUserResultMap">
<!-- orders类中的属性进行映射 -->
<id column="id" property="id"/>
<!-- orders类中的userId对应table中的user_id column:别名 -->
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!-- 关联信息的映射
association:用于关联到单个的pojo
property:要将关联信息映射到orders的哪个属性中
javaType:关联信息映射到orders的属性的类型,是user类型
-->
<association property="user" javaType="user">
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
</association>
</resultMap>
//SqlMapConfig.xml中定义别名 没有定义别名记得写全限定名
<typeAlias type="cn.itcast.mybatis.pojo.Orders" alias="orders"/>
<typeAlias type="cn.itcast.mybatis.pojo.User" alias="user"/>
resultType:要自定义pojo保证sql查询列和pojo的属性对应,这种方法简单
resultMap:使用association完成一对一映射需要设置一个resultMap,如果要实现延迟加载就只能用resultMap实现。
resultMap实现一对多查询:
// 查询语句:select orders.*,user.username,user.sex from orders,user where orders.user_id = user.id;
//在这个语句的基础上加一个表关联
select orders.*,user.username,user.sex,orderdetail.id ordertail_id,orderdetail.items_num,orderdetail.items_id from orders,user,orderdetail where orders.user_id = user.id and orders_id = orderdetail.orders_id;
public class Orders{
private Integer id;
private Integer userId;
private String number;
private Date createtime;
private String note;
//关联用户信息
private User user;
private List<Orderdetail> orderdetails;
}
resultMap定义:
<!-- 一对多查询resultMap定义
extends:我们上面定义的一对一resultMap
ofType:集合中的pojo类型
-->
<resultMap type="orders" id="orderAndOrderDetails" extends="ordersUserResultMap">
<collection property="orderdetails" ofType="cn.itcast.mybatis.pojo.Orderdetail">
<id column="orderdetail_id" property="id"/>
<result column="items_num" property="itemsNum"/>
<result column="items_id" property="itemsId"/>
</collection>
</resultMap>
//注意这里用的是collection !! 不是association