文章目录
Mybatis简介
原始jdbc操作问题
- 数据库连接的频繁创建和销毁带来的资源浪费
- sql语句在代码中耦合性太大,代码不易维护
- 需要手动的将数据库查询的数据和实体数据进行对应字段的映射
解决方案
- 使用数据库连接池
- 将sql抽取到xml配置文件当中
- 使用反射、内省等底层技术,自动将实体与表进行属性和字段的自动映射
什么是Mybatis
- Mybatis是一个基于Java的持久层框架
- 可以隐藏 jdbc 繁杂的 api,只需要关注 sql 语句的编写,而不需要关注 加载驱动、创建连接等繁杂的过程
- 可以将 sql 执行的结果映射为 java 对象返回,使用了 ORM(Object Relation Mapping) 思想,解决实体与数据库映射的问题
Mybatis开发步骤
- 添加Mybatis和mysql驱动jar包
- 创建user表
- 编写User实体类
- 编写映射文件UserMapper.xml(写Sql语句)
- 编写核心配置文件SqlMapConfig.xml
- 测试
Mybatis映射文件 xxxMapper.xml
- 主要用来配置Sql语句
需要在顶部加入约束
<?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">
- 使用sqlSession执行sql时需要传入
namespace+id
实现 - 插入,删除,修改操作涉及数据库数据变化,需要使用sqlSession显示的提交事务
sqlSession.commit()
查找
namespace:查找的命名空间
resultType:指定查找到的数据封装成的实体类型
<mapper namespace="userMapper">
<select id="findAll" resultType="com.itheima.domain.User">
select * from user
</select>
</mapper>
//执行操作 参数 namespace+id
List<User> userList = sqlSession.selectList("userMapper.findAll");
插入
parameterType:指定传入参数的类型
sql语句中使用#{实体属性名}
方式引用实体的属性值
<mapper namespace="userMapper">
<insert id="save" parameterType="com.itheima.domain.User">
insert into user values(#{id}, #{username}, #{password})
</insert>
</mapper>
sqlSession.insert("userMapper.save", user);
sqlSession.commit();
修改
<mapper namespace="userMapper">
<update id="update" parameterType="com.itheima.domain.User">
update user set username = #{username}, password = #{password} where id = #{id}
</update>
</mapper>
sqlSession.update("userMapper.update", user);
sqlSession.commit();
删除
当parameterType传递的是单个参数时,#{任意名字}
引用传递的单个参数
<mapper namespace="userMapper">
<delete id="delete" parameterType="java.lang.Integer">
delete from user where id = #{id}
</delete>
</mapper>
sqlSession.delete("userMapper.delete", 3);
sqlSession.commit();
Mybatis核心配置文件 SqlMapConfig.xml
environment标签
default:指定默认使用的 数据库 环境
id:当前环境的名称
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
mapper标签
作用:是加载映射的标签
相对于类路径的资源引用: <mapper resource="com.itheima.mapper\UserMapper.xml"/>
properties标签
作用:加载额外的配置文件,如 jdbc.properties
<properties resource="jdbc.properties"/>
typeAliases标签
作用: 为 Java 类型设置一个别名
Mybatis已经定义好的别名
自定义别名
SqlMapConfig.xml
<typeAliases>
<typeAlias type="com.itheima.domain.User" alias="user"/>
</typeAliases>
UserMapper.xml
<select id="findAll" resultType="user">
select * from user
</select>
typeHandlers标签
- 作用:重写类型处理器或自定义类型处理器来处理 java 数据类型和 mysql 数据库的数据类型的转换
- 开发步骤:
- 定义处理器类
extends BaseTypeHandler<T>
- 覆盖四个未实现的方法
setNonNullParameter
是java类型转换为数据库类型的方法
getNullableResult
是 mysql类型转换为 java的Type类型的方法 - 在
sqlMapConfig.xml
中进行typeHandler的注册
- 定义处理器类
- 例如:将一个 java 中的 Date 类型转换为 毫秒值存入mysql 的bigint,再将 mysql 中取出的 bigint 数据转换为 Date 类型
DateTypeHandler.java
public class DateTypeHandler extends BaseTypeHandler<Date> {
//java类型转换为数据库需要的类型
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Date date, JdbcType jdbcType) throws SQLException {
long time = date.getTime();
preparedStatement.setLong(i, time);
}
//数据库中类型转换为java类型
public Date getNullableResult(ResultSet resultSet, String s) throws SQLException {
long aLong = resultSet.getLong(s);
return new Date(aLong);
}
//数据库中类型转换为java类型
public Date getNullableResult(ResultSet resultSet, int i) throws SQLException {
long aLong = resultSet.getLong(i);
return new Date(aLong);
}
//数据库中类型转换为java类型
public Date getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
long aLong = callableStatement.getLong(i);
return new Date(aLong);
}
}
sqlMapConfig.xml
<!--定义类型转换处理器-->
<typeHandlers>
<typeHandler handler="com.itheima.handler.DateTypeHandler"/>
</typeHandlers>
plugins标签
- 作用:导入外部插件
- 例如:pageHelper插件简化分页操作
- 使用步骤:
- 导入pageHelper坐标 和 jsqlparser坐标
- 在mybatis的核心配置文件中配置pageHelper插件
- 测试
配置插件
<!--配置pageHelper-->
<plugins>
<plugin interceptor="com.github.pagehelper.PageHelper">
<!--定义方言,不同的数据库的查询关键字不同-->
<property name="dialect" value="mysql"/>
</plugin>
</plugins>
测试
//配置分页参数 当前页 + 页面大小
PageHelper.startPage(2, 3);
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.findAll();
for (User user : userList) {
System.out.println(user);
}
//获取分页信息
PageInfo<User> pageInfo = new PageInfo<User>(userList);
System.out.println("总条数:" + pageInfo.getTotal());
System.out.println("总页数:" + pageInfo.getPages());
System.out.println("当前页:" + pageInfo.getPageNum());
Mybatis相关API
SqlSessionFactoryBuilder构建SqlSessionFactory
通过加载mybatis核心文件的输入流的形式构建一个SqlSessionFactory对象
//加载核心配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
//获得session工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
Resources工具类,从类文件下加载资源文件,在org.apache.ibatis.io包中
SqlSessionFactory对象创建SqlSession对象
openSession()
默认开启一个事务,但不会自动提交
openSession(boolean autoCommit)
参数为是否自动提交,true则会自动提交
SqlSession会话对象
执行语句的方法
操作事务的方法
Mybatis的Dao层实现
手动对Dao进行实现(不常用)
Mybatis动态代理方式对Dao进行实现:代理生成接口的实现类
- 在Dao中调用:
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.findAll();
- 注意:
namespace 与 接口的全限定名 对应
id 与 接口中方法名 对应
parameterType 与 接口中方法参数类型 一致
resultType 与 接口中返回类型 一致
Mybatis映射文件深入
动态Sql语句
<if>
<where>
用于where查询条件需要变化时
<where>
标签相当于where 1 = 1
,没有条件时sql后不加where
<if>
标签相当于判断,必需test属性
<select id="findByCondition" resultType="user" parameterType="user">
select * from user
<where>
<if test="id != 0">
and id = #{id}
</if>
<if test="username != null">
and username = #{username}
</if>
<if test="password != null">
and password = #{password}
</if>
</where>
</select>
<foreach>
多用于查询多个同类条件,如select * from user where id in (1, 2, 3)
foreach
属性
collection:集合类型,数组用array,List用list
open:循环前要加的字符串
close:循环后要加的字符串
separator:循环间隔符
item:负责接收集合中的每一个值,可以随意命名,类似于java的增强for
<select id="findByIds" parameterType="list" resultType="user">
select * from user
<where>
<foreach collection="list" open="id in(" close=")" separator="," item="id">
#{id}
</foreach>
</where>
</select>
Sql片段的抽取
解耦合
<!--抽取的片段-->
<sql id="selectUser">select * from user</sql>
-- 引用片段
<include refid="selectUser"/>
Mybatis多表查询
一对一查询
-
用户和订单的关系:一个用户有多个订单,一个订单只属于一个用户
那么查询一个订单的用户为 一对一查询 -
数据库中:orders表有主键 id 外键 uid,user表有主键 id
实体类 Order,User
public class Order {
private int id;
private Date orderTime;
private double total;
//当前订单属于哪一个用户
private User user;
//getter+setter...
}
public class User {
private int id;
private String username;
private String password;
private Date birthday;
}
OrderMapper.xml
<mapper namespace="com.itheima.mapper.OrderMapper">
<!--手动指定要封装的映射类型
column:数据库字段名
property:实体属性名
-->
<resultMap id="orderMap" type="order">
<id column="id" property="id"/>
<result column="ordertime" property="orderTime"/>
<result column="total" property="total"/>
<!--
property:order中的属性名称(private User user)
javaType:order中的属性的类型(User)
-->
<association property="user" javaType="user">
<id column="uid" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="birthday" property="birthday"/>
</association>
</resultMap>
<select id="findAll" resultMap="orderMap">
select *, o.id oid from orders o, user u where u.id = o.uid
</select>
</mapper>
此处的 resultMap的type 和 association的javaType 用typeAliases指定了别名
一对多查询
- 查询一个用户的所有订单
实体类 User 中包含 List<Order> orderList
public class User {
private int id;
private String username;
private String password;
private Date birthday;
//该用户所有订单
private List<Order> orderList;
//getter+setter...
}
OrderMapper.xml
<mapper namespace="com.itheima.mapper.UserMapper">
<resultMap id="userMap" type="user">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="birthday" property="birthday"/>
<!--配置集合信息
property:指定集合名称
ofType:指定集合中的数据类型
-->
<collection property="orderList" ofType="order">
<!--封装order的数据-->
<id column="oid" property="id"/>
<result column="ordertime" property="orderTime"/>
<result column="total" property="total"/>
</collection>
</resultMap>
<select id="findAll" resultMap="userMap">
select *, o.id oid from user u, orders o where u.id = o.uid
</select>
</mapper>
此处的 resultMap 的 Type 和 collection 的 ofType 使用 typeAliases 指定了别名
多对多查询
- 与一对多查询类似,只是使用了一个中间表将两个多对多的表的主键作为主键,同时设置为两个表的外键
Mybatis注解开发
常用注解
配置到XxxMapper接口文件的对应方法上即可
一对一查询
public interface OrderMapper {
@Select("select * from orders")
@Results({
@Result(id = true, property = "id",column = "id"), //id = true表示是 id 标签
@Result(property = "ordertime",column = "ordertime"),
@Result(property = "total",column = "total"),
@Result(property = "user", //要封装的属性名称
column = "uid", //根据哪个字段去查询user表
javaType = User.class, //要封装的实体类型
one = @One(select = "com.itheima.mapper.UserMapper.findById")) //select代表查询哪个接口的方法获得数据
})
List<Order> findAll();
}
public interface UserMapper {
@Select("select * from user where id=#{id}")
User findById(int id);
}
一对多查询
public interface UserMapper {
@Select("select * from user")
@Results({
@Result(id = true, property = "id",column = "id"),
@Result(property = "username",column = "username"),
@Result(property = "password",column = "password"),
@Result(property = "birthday",column = "birthday"),
@Result(property = "orderList",
column = "id",
javaType = List.class,
many = @Many(select = "com.itheima.mapper.OrderMapper.findByUid"))
})
List<User> findAllUserAndOrder();
}
public interface OrderMapper {
@Select("select * from orders where uid=#{uid}")
List<Order> findByUid(int uid);
}
多对多查询
- 与一对多查询类似,SQL语句略有不同
SSM整合
需要添加 mybatis-spring 的 jar 包坐标
- 思路:主要就是将SqlSessionFactory交给Spring管理 并 将事务控制使用Spring的声明式事务控制来管理,减少重复代码编写
- 将sqlSessionFactory配置到Spring容器中
<!--加载jdbc.properties-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置jdbc模板对象-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--配置MyBatis的SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:sqlMapConfig.xml"/>
</bean>
- 扫描 Mapper,让Spring容器产生Mapper类
<!--配置Mapper扫描-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.itheima.mapper"/>
</bean>
- 配置声明式事务控制
<!--配置声明式事务控制-->
<bean id="transacionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="transacionManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* com.itheima.service.impl.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
- 修改Service实现代码
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
public void save(Account account) {
accountMapper.save(account);
}
public List<Account> findAll() {
return accountMapper.findAll();
}
}
总结
- 映射文件 XxxMapper.xml 主要是进行sql语句的编写,然后对Mapper包下的接口使用动态代理的方式实现对数据库的操作,替换掉了以前的原始 dao操作。
开发中常常使用注解直接在接口方法上进行sql语句的编写配置 - 核心配置文件 sqlMapConfig.xml 主要是进行
加载配置文件(properties)
定义别名(typeAliases)
定义数据处理转换器(typeHandlers)
加载其他插件(plugins)
数据库环境的指定(environments)
加载映射文件(mappers) - 动态SQL语句可以使用于查询条件发生改变时的情况,常用于多条件查询;查询多个同类条件用 foreach , 查询条件会变化用 if,多个重复 SQL 片段可以抽取
- 一对一,一对多,多对多查询的
resultType
一般用resultMap
代替,resultMap 中显示的写出每一个关联的参数。
常常使用注解@Results
和@Result
代替resultMap
- Mybatis整合进Spring,主要就是将SqlSessionFactory交给Spring容器管理 并 将事务控制使用Spring的声明式事务控制来管理。
参考
2020年IDEA版黑马Java :BV1WZ4y1H7du