Mybatis 是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc ,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。
Mybatis 通过 xml 或注解的方式将要执行的各种 statement 配置起来,并通过java对象和 statement 中sql的动态参数进行映射生成最终执行的 sql 语句,最后由 Mybatis 框架执行 sql 并将结果映射为 java 对象并返回。
采用ORM
思想解决了实体和数据库映射的问题,对 jdbc 进行了封装,屏蔽了jdbc api底层访问细节,使我们不用与 jdbc api打交道,就可以完成对数据库的持久化操作。
持久层技术解决方案
JDBC技术:
Spring的 JdbcTemplate:
Apache 的 DBUtils:
以上这些都不是框架,JDBC是规范,Spring 的 JdbcTemplate 和 Apache 的 DBUtils 都只是工具类
ORM
Object Relational Mappging 对象关系映射
简单的说:
就是把数据库表和实体类及实体类的属性对应起来
让我们可以操作实体类就实现操作数据库表。
创建 maven 工程并导入坐标
创建实体类和 dao 的接口
创建 Mybatis 的主配置文件 :SqlMapConifg.xml
创建映射配置文件:IUserDao.xml
@Select
注解,并指定 sql ,便可以去掉该配置文件(使用注解就得去掉)SqlMapConfig.xml
中的 mapper 配置时,使用 class 属性指定dao接口的全限定类名。public interface IUserDao {
@Select("select * from user")
List<User> findAll();
}
<mappers>
<mapper class="com.leyou.dao.IUserDao"/>
mappers>
Mapper 文件
以下内容为 IUserDao.xml
Mapper
。所以:IUserDao
,也可以写成IUserMapper
<mapper namespace="com.leyou.dao.IUserDao">
<select id="findAll" resultType="com.leyou.domain.User">
select * from user select>
mapper>
<configuration>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC">transactionManager>
<dataSource type="POOLED">
<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="1234"/>
dataSource>
environment>
environments>
<mappers>
<mapper class="com.leyou.dao.IUserDao"/>
mappers>
configuration>
程序编写的思路
SqlSessionFactory
工厂SqlSession
Dao
接口的代理对象Dao
中的方法 /**
* 入门案例
* @param args
*/
public static void main(String[] args)throws Exception {
//1.读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.使用工厂生产SqlSession对象
SqlSession session = factory.openSession();
//4.使用SqlSession创建Dao接口的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);
//5.使用代理对象执行方法
List<User> users = userDao.findAll();
for(User user : users){
System.out.println(user);
}
//6.释放资源
session.close();
in.close();
}
}
sqlSession 深入
SqlSession 的语句执行方法
注意
namespace.方法
。selectOne 和selectList 的不同仅仅是 selectOne 必须返回一个对象。如果多余一个,或者没有返回(或返回了null),那么就会抛出异常。如果你不知道需要多少对象,使用 selectList 。如果你想检查一个对象是否存在,那么最好返回统计数(0或1)。
因为并不是所有语句都需要参数,这些方法都是有不同重载版本的,它们可以不需要参数对象。
参考
SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 实例的最佳范围是**方法范围(**也就是局部方法变量)。你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但是最好还是不要让其一直存在以保证所有的 XML 解析资源开放给更重要的事情。
SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建。使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏味道(bad smell)”。因此 SqlSessionFactory 的最佳范围是应用范围。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的范围是请求或方法范围。绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession 实例的引用放在任何类型的管理范围中,比如 Serlvet 架构中的HttpSession。如果你现在正在使用一种 Web 框架,要考虑 SqlSession 放在一个和 HTTP 请求对象相似的范围中。换句话说,每次收到的 HTTP 请求,就可以打开一个 SqlSession,返回一个响应,就关闭它。这个关闭操作是很重要的,你应该把这个关闭操作放到 finally 块中以确保每次都能执行关闭。
使用 MyBatis-Spring 之后,你不再需要直接使用 SqlSessionFactory
了,因为你的 bean 可以被注入一个线程安全的 SqlSession
,它能基于 Spring 的事务配置来自动提交、回滚、关闭 session。官方文档
CRUD
模糊查询
select * from user where username like #{username}
%
来作为模糊查询的条件,{username}
也只是一个占位符,所以 sql 语句显示为?
select * from user where username like '%${value}%'
${value}
的写法就是固定的,不能写成其它名字。
#{}
与${}
的区别
#{}
表示一个占位符号 通过#{}
可以实现preparedStatement
向占位符中设置值,自动进行 java 类型和 jdbc类型转换,#{}
可以有效防止 sql 注入。
#{}
可以接收简单类型值或 pojo 属性值。 如果parameterType
传输单个简单类型值,#{}
括号中可以是value或其它名称。${}
表示拼接 sql 串 通过${}
可以将parameterType
传入的内容拼接在 sql 中且不进行 jdbc 类型转换。
${}
可以接收简单类型值或pojo属性值,如果parameterType
传输单个简单类型值,${}
括号中只能是 value。以查询为例
<select id="findById" parameterType="INT" resultMap="userMap">
select * from user where id = #{uid}
select>
id="findAll"
:sql 语句所要应用 在 Dao接口的 方法名parameterType="INT"
:参数类型。传入一个类的对象,就写类的全名称(注册可使用别名)。大小写不敏感。(常使用包装类)resultType="int"
:可以指定结果集的类型,它支持基本类型和实体类类型,规则同parameterType
。
Mysql
在windows系统中不区分大小写!#{}
字符来表示占位符,它用的是 ognl 表达式resultMap="userMap"
:结果类型,建立查询的列名和实体类的属性名称不一致时建立对应关系
获取新增用户 id 的返回值
新增用户后,同时还要返回当前新增用户的id值,因为id是由数据库的自动增长来实现的,所以就相当于我们要在新增后将自动增长 auto_increment
的值返回。
<insert id="saveUser" parameterType="user">
<selectKey keyProperty="userId" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id();
selectKey>
insert into user(username,address,sex,birthday)values(#{userName},#{userAddress},#{userSex},#{userBirthday});
insert>
order
:定义获取 id 的时间
BEFORE
:插入前AFTER
:插入后ognl
它是 apache 提供的一种表达式语言,全称是:Object Graphic Navigation Language
对象图导航语言
#{对象.对象}
的方式
#{user.username}
它会先去找 user 对象,然后在 user 对象中找到 username 属性,并调用getUsername()
方法把值取出来。parameterType
属性上指定了实体类名称,所以可以省略user.
而直接写username
。方式一:使用别名
<select id="findAll" resultType="com.leyou.domain.User">
select id as userId,username as userName,birthday as userBirthday, sex as userSex,address as userAddress from user
select>
该方法效率最高,但是如果查询很多,开发起来不是很方便
方式二:resultMap
resultMap 标签可以建立查询的列名和实体类的属性名称不一致时建立对应关系。从而实现封装。
<resultMap type="com.leyou.domain.User" id="userMap">
<id column="id" property="userId"/>
<result column="username" property="userName"/>
<result column="sex" property="userSex"/>
resultMap>
Mapper 配置文件参考
<mapper namespace="com.leyou.dao.IUserDao">
<resultMap id="userMap" type="com.leyou.domain.User">
<id property="userId" column="id">id>
<result property="userName" column="username">result>
<result property="userAddress" column="address">result>
<result property="userSex" column="sex">result>
<result property="userBirthday" column="birthday">result>
resultMap>
<select id="findAll" resultMap="userMap">
select * from user;
select>
<insert id="saveUser" parameterType="user">
<selectKey keyProperty="userId" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id();
selectKey>
insert into user(username,address,sex,birthday)values(#{userName},#{userAddress},#{userSex},#{userBirthday});
insert>
<update id="updateUser" parameterType="USER">
update user set username=#{userName},address=#{userAddress},sex=#{userAex},birthday=#{userBirthday} where id=#{userId}
update>
<delete id="deleteUser" parameterType="java.lang.Integer">
delete from user where id = #{uid}
delete>
<select id="findById" parameterType="INT" resultMap="userMap">
select * from user where id = #{uid}
select>
<select id="findByName" parameterType="string" resultMap="userMap">
select * from user where username like #{name}
select>
<select id="findTotal" resultType="int">
select count(id) from user;
select>
<select id="findUserByVo" parameterType="com.leyou.domain.QueryVo" resultMap="userMap">
select * from user where username like #{user.username}
select>
mapper>
-properties(属性)
--property
-settings(全局配置参数)
--setting
-typeAliases(类型别名)
--typeAliase
--package
-typeHandlers(类型处理器)
-objectFactory(对象工厂)
-plugins(插件)
-environments(环境集合属性对象)
--environment(环境子属性对象)
---transactionManager(事务管理)
---dataSource(数据源)
-mappers(映射器)
--mapper
--package
我们可以采用两种方式指定属性配置。
<properties>
<property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="jdbc.url" value="jdbc:mysql://localhost:3306/eesy"/>
<property name="jdbc.username" value="root"/> <
property name="jdbc.password" value="1234"/>
properties>
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy
jdbc.username=root
jdbc.password=1234
<properties url="file:///D:/IdeaProjects/day02_eesy_01mybatisCRUD/src/main/resources/jdbcConfig.properties">
properties>
最后,配置 datasource
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}">property>
<property name="url" value="${jdbc.url}">property>
<property name="username" value="${jdbc.username}">property>
<property name="password" value="${jdbc.password}">property>
dataSource>
:单个别名定义
:批量给包 别名
<typeAliases>
<typeAlias type="com.leyou.domain.User" alias="user">typeAlias>
<package name="com.leyou.domain">package>
typeAliases>
resource
使用相对于类路径的资源 ,如:
<mappers>
<mapper resource="com/leyou/dao/IUserDao.xml" /> mappers>
class
使用mapper接口类路径,此种方法要求 mapper 接口名称和 mapper映射文件名称相同,且放在同一个目录中。
<mappers>
<mapper class="com.leyou.dao.UserDao"/> mappers>
package
注册指定包下的所有 mapper 接口,此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。
<mappers>
<package name="com.leyou.dao">package> mappers>
设置(settings)
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。
cacheEnabled
:全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。默认 truelazyLoadingEnabled
:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType
属性来覆盖该项的开关状态。默认 falseaggressiveLazyLoading
:开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载。默认 false类型处理器(typeHandlers)
MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。
对象工厂(objectFactory)
每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。 如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现。
插件(plugins)
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为在试图修改或重写已有方法的行为时,很可能会破坏 MyBatis 的核心模块。 这些都是更底层的类和方法,所以使用插件的时候要特别当心。
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。
环境配置(environments)
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。
事务管理器(transactionManager)
在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"
):
如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。
数据源(dataSource)
dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
type="[UNPOOLED|POOLED|JNDI]"
)常用注解
具体实现
使用注解开发,则不需要 dao.xml, 但仍需要 主配置文件 sqlMapConfig.xml
直接在接口上使用标签并编写 sql 语句
/**
* 保存用户
* @param user
*/
@Insert("insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday})")
void saveUser(User user);
/**
* 更新用户
* @param user
*/
@Update("update user set username=#{username},sex=#{sex},birthday=#{birthday},address=#{address} where id=#{id}")
void updateUser(User user);
/**
* 删除用户
* @param userId
*/
@Delete("delete from user where id=#{id} ")
void deleteUser(Integer userId);
/**
* 根据id查询用户
* @param userId
* @return
*/
@Select("select * from user where id=#{id} ")
User findById(Integer userId);
/**
* 根据用户名称模糊查询
* @param username
* @return
*/
@Select("select * from user where username like #{username} ")
//@Select("select * from user where username like '%${value}%' ")
List<User> findUserByName(String username);
/**
* 查询总用户数量
* @return
*/
@Select("select count(*) from user ")
int findTotalUser();
主配置文件中需要配置
<mappers>
<mapper class="com.leyou.dao.IUserDao">mapper>
mappers>
我们在实际开发中都会使用连接池。因为它可以减少我们获取连接所消耗的时间。而在 Mybatis 中也有连接池技术,但是它采用的是自己的连接池技术。
池思想示意图
分类
POOLED
:常用。采用传统的 javax.sql.DataSource
规范中的连接池,mybatis 中有针对规范的实现。UNPOOLED
:采用传统的获取连接的方式(不使用连接池),虽然也实现 Javax.sql.DataSource
接口,但是并没有使用池的思想。JNDI
:采用服务器提供的 JNDI 技术实现,来获取 DataSource 对象,不同的服务器所能拿到 DataSource 是不一样。JNDI 注意
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}">property>
<property name="url" value="${jdbc.url}">property>
<property name="username" value="${jdbc.username}">property>
<property name="password" value="${jdbc.password}">property>
dataSource>
JNDI:Java Naming and Directory Interface
。是SUN公司推出的一套规范,属于 JavaEE 技术之一。目的是模仿 windows 系统中的注册表。百科
tomcat 已经集成该服务(内置并默认使用DBCP连接池),简单来说就是键值对的 mapping,而且在 tomcat 服务器启动的首页 configuration
中就已经有完成的示例代码。要想使用 tomcat 的 JNDI 服务,只需要导入相关的 jar 包,建立所需的配置文件,采用 JDK 的命名服务 API 根据配置名称即可获得相应的服务。
建议参考
比较
没有JNDI的做法:
程序员开发时,知道要开发访问MySQL数据库的应用,于是将一个对 MySQL JDBC 驱动程序类的引用进行了编码,并通过使用适当的 JDBC URL 连接到数据库。
用了JNDI之后的做法:
首先,在在 J2EE 容器中配置 JNDI 参数,定义一个数据源,也就是 JDBC 引用参数,给这个数据源设置一个名称;然后,在程序中,通过数据源名称引用数据源从而访问后台数据库。
配置
在 Mybatis 的SqlMapConfig.xml
配置文件中,通过
来实现 Mybatis 中连接池的配置。
MyBatis在初始化时,根据
的 type 属性来创建相应类型的的数据源 DataSource。
type=”POOLED”
:会创建 PooledDataSource 实例type=”UNPOOLED”
:会创建 UnpooledDataSource 实例type=”JNDI”
:会从 JNDI 服务上查找 DataSource 实例,然后返回使用DataSource 的存取
MyBatis 是通过工厂模式来创建数据源 DataSource 对象的,
MyBatis定义了抽象的工厂接口:org.apache.ibatis.datasource.DataSourceFactory
,通过其getDataSource()
方法返回数据源DataSource。
过程
当我们需要创建 SqlSession 对象并需要执行 SQL 语句时,这时候 MyBatis才会去调用 dataSource对象来创建java.sql.Connection
对象。也就是说,java.sql.Connection
对象的创建一直延迟到执行 SQL 语句的时候。
@Test public void testSql() throws Exception {
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = factory.openSession();
List<User> list = sqlSession.selectList("findUserById",41);
System.out.println(list.size()); }
只有当第4句sqlSession.selectList("findUserById")
,才会触发 MyBatis 在底层执行下面这个方法来创建java.sql.Connection
对象。
我们可以通过断点调试,在PooledDataSource
中找到如下popConnection()
方法
简而言之
只有在我们执行 sql 语句时,才会去获取并打开连接。
什么是事务?
如果一个包含多个步骤的业务操作,被事务管理,那么这些操作要么同时成功,要么同时失败。
事务的四大特性 ACID
隔离性会产生的3个问题
不可重复度和幻读区别:
解决办法:四种隔离级别
read uncommitted
:读未提交
read committed
:读已提交 (Oracle)
repeatable read
:可重复读 (MySQL默认)
serializable
:串行化
实现方式
通过 sqlsession 对象的 commit 方法和 rollback 方法实现事务的提交和回滚。
事务提交方式
@Before//用于在测试方法执行之前执行
public void init()throws Exception{
//1.读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3.获取SqlSession对象
sqlSession = factory.openSession();
//4.获取dao的代理对象
userDao = sqlSession.getMapper(IUserDao.class);
}
setAutoCommit()
方法就可以调整setAutoCommit()
来实现事务控制。
session = factory.openSession(true);
来自动提交connection.setAutoCommit(false)
,所以需要手动提交。我们根据实体类的不同取值,使用不同的 SQL 语句来进行查询。
标签的 test 属性中写的是对象的属性名,如果是包装类的对象要使用 OGNL 表达式的写法。where 1=1
的作用:因 where 后的筛选条件是不确定的(0或n),该式子,很好地解决了后续条件的拼接问题。<select id="findUserByCondition" resultMap="userMap" parameterType="user">
select * from user where 1=1
<if test="userName != null">
and username = #{userName}
if>
<if test="userSex != null">
and sex = #{userSex}
if>
select>
为了简化上面where 1=1
的条件拼装,我们可以采用
标签来简化开发。
<select id="findUserByCondition" resultMap="userMap" parameterType="user">
select * from user
<where>
<if test="userName != null">
and username = #{userName}
if>
<if test="userSex != null">
and sex = #{userSex}
if>
where>
select>
SQL语句: select 字段from user where id in (?)
标签用于遍历集合,它的属性:
collection
: 代表要遍历的集合元素,注意编写时不要写#{}
open
: 代表语句的开始部分close
代表结束部分item
: 代表遍历集合的每个元素,生成的变量名sperator
: 代表分隔符抽取重复代码
<sql id="defaultUser">
select * from user
sql>
<select id="findUserInIds" resultMap="userMap" parameterType="queryvo">
<include refid="defaultUser">include>
<where>
<if test="ids != null and ids.size()>0">
<foreach collection="ids" open="and id in (" close=")" item="uid" separator=",">
#{uid}
foreach>
if>
where>
select>
@SelectProvider: 实现动态SQL映射。参考
简易示例
SelectSqlProvider:
public class SelectSqlProvider {
public String selectByUserId(Long id) {
StringBuffer buffer = new StringBuffer();
buffer.append("SELECT * FROM user where ");
buffer.append("id = ").append(id).append(";");
return buffer.toString();
}
}
UserMapper:
@ResultMap("BaseResultMap")
@SelectProvider(type = SelectSqlProvider.class, method = "selectByUserId")
User getUserByUserId(long id);
这一个方法在 xml 中没有对应的 sql ,在该方法上也没有 @Select 注解修饰,只有 @SelectProvider 注解,@SelectProvider 中两个属性,type 为提供 sql 的 class 类,method 为指定方法。
多表之间的关系分类
实现方式:
定义专门的 po 类(AccountUser)作为输出类型,其中定义了 sql 查询结果集所有的字段。(编写一个子类方式)
resultMap
,查询时直接使用 resultType
。使用 resultMap ,定义专门的 resultMap 用于映射一对一查询结果。(从表实体类中引用一个主表实体类对象)
说明
association
来建立一对一(多对一)关系映射javaType
定义封装到的对象
<resultMap id="accountUserMap" type="account">
<id property="id" column="aid">id>
<result property="uid" column="uid">result>
<result property="money" column="money">result>
<association property="user" column="uid" javaType="user">
<id property="id" column="id">id>
<result column="username" property="username">result>
<result column="address" property="address">result>
<result column="sex" property="sex">result>
<result column="birthday" property="birthday">result>
association>
resultMap>
<select id="findAll" resultMap="accountUserMap">
select u.*,a.id as aid,a.uid,a.money from account a , user u where u.id = a.uid;
select>
<select id="findAllAccount" resultType="accountuser">
select a.*,u.username,u.address from account a , user u where u.id = a.uid;
select>
实现方式:
建立两张表:用户表,账户表;让用户表和账户表之间具备一对多的关系:需要使用外键在账户表中添加
建立两个实体类:用户实体类和账户实体类;让用户和账户的实体类能体现出来一对多的关系
List
说明
collection
部分定义了用户关联的账户信息。表示关联查询结果集 ,一对多的关系使用
property="accounts"
: 关联查询的结果集存储在 User 对象的上哪个属性。ofType="account"
: 指定关联查询的结果集中的对象类型即 List 中的对象类型。此处可以使用别名,也可以使用全限定名。
<resultMap id="userAccountMap" type="user">
<id property="id" column="id">id>
<result property="username" column="username">result>
<result property="address" column="address">result>
<result property="sex" column="sex">result>
<result property="birthday" column="birthday">result>
<collection property="accounts" ofType="account">
<id column="aid" property="id">id>
<result column="uid" property="uid">result>
<result column="money" property="money">result>
collection>
resultMap>
<select id="findAll" resultMap="userAccountMap">
select * from user u left outer join account a on u.id = a.uid
select>
<select id="findById" parameterType="INT" resultType="user">
select * from user where id = #{uid}
select>
实现方式:
<resultMap id="userMap" type="user">
<id property="id" column="id">id>
<result property="username" column="username">result>
<result property="address" column="address">result>
<result property="sex" column="sex">result>
<result property="birthday" column="birthday">result>
<collection property="roles" ofType="role">
<id property="roleId" column="rid">id>
<result property="roleName" column="role_name">result>
<result property="roleDesc" column="role_desc">result>
collection>
resultMap>
<select id="findAll" resultMap="userMap">
select u.*,r.id as rid,r.role_name,r.role_desc from user u
left outer join user_role ur on u.id = ur.uid
left outer join role r on r.id = ur.rid
select>
<resultMap id="roleMap" type="role">
<id property="roleId" column="rid">id>
<result property="roleName" column="role_name">result>
<result property="roleDesc" column="role_desc">result>
<collection property="users" ofType="user">
<id column="id" property="id">id>
<result column="username" property="username">result>
<result column="address" property="address">result>
<result column="sex" property="sex">result>
<result column="birthday" property="birthday">result>
collection>
resultMap>
<select id="findAll" resultMap="roleMap">
select u.*,r.id as rid,r.role_name,r.role_desc from role r
left outer join user_role ur on r.id = ur.rid
left outer join user u on u.id = ur.uid
select>
实现复杂关系映射之前我们可以在映射文件中通过配置
来实现,在使用注解开发时我们需要借助@Results注解
,@Result注解
,@One注解
,@Many注解
。
@Results 注解
代替的是标签
,该注解中可以使用单个@Result
注解,也可以使用@Result
集合
id="userMap"
,供其它接口引用@Resutl 注解
代替了
标签和
标签
id
是否是主键字段column
数据库的列名property
需要装配的属性名one
需要使用的@One 注解(@Result(one=@One)()))
many
需要使用的@Many 注解(@Result(many=@many)()))
@ResultMap注解
实现引用 @Results
定义的封装,
@ResultMap("userMap")
,这是省略写法(单个的情况下)@ResultMap(value={"userMap","userMap2"})
/**
* 查询所有用户
* @return
*/
@Select("select * from user")
@Results(id="userMap",value={
@Result(id=true,column = "id",property = "userId"),
@Result(column = "username",property = "userName"),
@Result(column = "address",property = "userAddress"),
@Result(column = "sex",property = "userSex"),
@Result(column = "birthday",property = "userBirthday"),
@Result(property = "accounts",column = "id",
many = @Many(select = "com.leyou.dao.IAccountDao.findAccountByUid",
fetchType = FetchType.LAZY))
})
List<User> findAll();
/**
* 根据id查询用户
* @param userId
* @return
*/
@Select("select * from user where id=#{id} ")
@ResultMap("userMap")
User findById(Integer userId);
@One 注解(一对一)
代替了
标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。
select
指定用来多表查询的 sqlmapperfetchType
会覆盖全局的配置参数 lazyLoadingEnabled
。如果要延迟加载都设置为LAZY
的值 @Result(property = "user",column = "uid",one=@One(select="com.leyou.dao.IUserDao.findById",fetchType= FetchType.EAGER)
@Many 注解(多对一)
代替了
标签,是是多表查询的关键,在注解中用来指定子查询返回对象集合。
javaType
(一般为 ArrayList)但是注解中可以不定义;@Result(property = "accounts",column = "id",
many = @Many(select = "com.leyou.dao.IAccountDao.findAccountByUid",
fetchType = FetchType.LAZY)
延迟加载:就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.
好处:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速
度要快。
坏处:
因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗
时间,所以可能造成用户等待时间变长,造成用户体验下降
使用要求
关联对象的查询与主加载对象的查询必须是分别进行的select语句,不能是使用多表连接所进行的select查询。因为,多表连接查询,实质是对一张表的查询,对由多个表连接后形成的一张表的查询。会一次性将多张表的所有信息查询出来。
实现原理
它的原理是,使用CGLIB
创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用 a.getB().getName()
,拦截器 invoke()
方法发现 a.getB()
是 null 值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName()
方法的调用。这就是延迟加载的基本原理。
当然了,不光是 Mybatis,几乎所有的包括 Hibernate,支持延迟加载的原理都是一样的。
延迟加载的实现
修改配置文件
实现原理:在需要查询时,调用对方配置文件 中的配置来实现查询功能,所以进行如下配置:
select="com.leyou.dao.IAccountDao.findAccountByUid"
,用于指定查询 account 列表的 sql 语句(账户的 dao 全限定类名加上方法名称)column="id">
,用于指定 select 属性的 sql 语句的参数来源修改 Dao.xml
<association property="user" column="uid" javaType="user" select="com.leyou.dao.IUserDao.findById">association>
<collection property="accounts" ofType="account" select="com.leyou.dao.IAccountDao.findAccountByUid" column="id">collection>
Mybatis 中默认是不开启延迟加载的,我们需要在 Mybatis 的主配置文件 SqlMapConfig.xml
文件中添加延迟加载的配置。
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false">setting>
settings>
总结
延迟加载:在真正使用数据时才发起查询,不用的时候不查询。按需加载(懒加载)
立即加载:不管用不用,只要一调用方法,马上发起查询。
在对应的四种表关系中:一对多,多对一,一对一,多对多
一对多,多对多:通常情况下我们都是采用延迟加载。
多对一,一对一:通常情况下我们都是采用立即加载。
什么是缓存?
什么样的数据能使用缓存,什么样的数据不能使用?
适用于缓存:
不适用于缓存:
什么是一级缓存?
它指的是Mybatis中 **SqlSession 对象的缓存。**当我们执行查询之后,查询的结果会同时存入到 SqlSession 为我们提供一块区域中。该区域的结构是一个Map。当我们再次查询同样的数据,mybatis 会先去 sqlsession 中查询是否有,有的话直接拿出来用。
特点:
什么是二级缓存?
它指的是 Mybatis 中 SqlSessionFactory 对象的缓存。由同一个 SqlSessionFactory 对象创建的 SqlSession 共享其缓存。建议参考
特点:
namespace
为单位的,不同namespace
下的操作互不影响。使用
在 SqlMapConfig.xml
文件开启二级缓存,默认为 true ,也可以不配置
<settings>
<setting name="cacheEnabled" value="true"/>
settings>
配置相关的 Mapper 映射文件
标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值。
将 UserDao.xml
映射文件中的标签中设置
useCache=”true”
代表当前这个 statement 要使用
二级缓存,如果不使用二级缓存可以设置为 false。
针对每次查询都需要最新的数据 sql,要设置成 useCache=false
,禁用二级缓存。
<mapper namespace="com.leyou.dao.IUserDao">
<cache/>
<select id="findAll" resultType="user">
select * from user
select>
<select id="findById" parameterType="INT" resultType="user" useCache="true">
select * from user where id = #{uid}
select>
<update id="updateUser" parameterType="user">
update user set username=#{username},address=#{address} where id=#{id}
update>
mapper>
使用注解方式
在 dao(mapper) 接口上 配置
@CacheNamespace(blocking = true)
public interface IUserDao {
}
注意
当我们在使用二级缓存时,所缓存的类一定要实现 java.io.Serializable
接口,这种就可以使用序列化
方式来保存对象
简单讲述几个常用方法
Select:
List select(T record);
:根据实体中的属性值进行查询T selectByPrimaryKey(Object key);
:根据主键字段进行查询List selectAll();
:查询全部结果,select(null)方法能达到同样的效果Insert:
int insert(T record);
:保存一个实体,null的属性也会保存,不会使用数据库默认值int insertSelective(T record);
:保存一个实体,null的属性不会保存,会使用数据库默认值区别:
Update:
int updateByPrimaryKey(T record);
:根据主键更新实体全部字段,null值会被更新int updateByPrimaryKeySelective(T record);
:根据主键更新属性不为null的值Delete:
int delete(T record);
:根据实体属性作为条件进行删除int deleteByPrimaryKey(Object key);
:根据主键字段进行删除Example 方法:
List selectByExample(Object example);
:根据Example条件进行查询Example.Criteria:
Example类用于构造复杂的筛选条件。它包含一个名为Criteria的内部静态类,它包含将在where子句中一起结合的条件列表。Criteria类的集合允许您生成几乎无限类型的where子句。
//初始化example对象
Example example = new Example(Brand.class);
Example.Criteria criteria = example.createCriteria();
//根据name模糊查询,或者根据首字母查询
if (StringUtils.isNotBlank(sortBy)) {
criteria.andLike("name", "%" + key + "%").orEqualTo("letter", key);
}
List<Brand> brands = this.brandMapper.selectByExample(example);
表实体类写法
@Table(name = "tb_sku")
public class Sku {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long spuId;
private String title;
private String images;
private Long price;
private String ownSpec;// 商品特殊规格的键值对
private String indexes;// 商品特殊规格的下标
private Boolean enable;// 是否有效,逻辑删除用
private Date createTime;// 创建时间
private Date lastUpdateTime;// 最后修改时间
@Transient //忽略字段
private Integer stock;// 库存
}
Spring Boot 集成 MyBatis, 分页插件 PageHelper, 通用 Mapper
/**
* 根据条件分页查询spu
*
* @param key
* @param saleable
* @param page
* @param rows
* @return
*/
public PageResult<SpuBo> querySpuBoByPage(String key, Boolean saleable, Integer page, Integer rows) {
Example example = new Example(Spu.class);
Example.Criteria criteria = example.createCriteria();
//添加查询条件
if (StringUtils.isNotBlank(key)) {
criteria.andLike("title", "%" + key + "%");
}
//添加上下架的过滤条件
if (saleable != null) {
criteria.andEqualTo("saleable", saleable);
}
//添加分页
PageHelper.startPage(page, rows);
//执行查询,获取spu集合
List<Spu> spus = this.spuMapper.selectByExample(example);
PageInfo<Spu> PageInfo = new PageInfo<>(spus);
//spu集合转化成spubo集合
List<SpuBo> spuBos = spus.stream().map(spu -> {
SpuBo spuBo = new SpuBo();
//copy共同属性的值到新对象
BeanUtils.copyProperties(spu, spuBo);
//查询品牌名称
Brand brand = this.brandMapper.selectByPrimaryKey(spu.getBrandId());
spuBo.setBname(brand.getName());
//查询分类名称
List<String> names = this.categoryService.queryNameByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));
spuBo.setCname(StringUtils.join(names, "/"));
return spuBo;//勿忘
}
).collect(Collectors.toList());
//返回pageResult
return new PageResult<>(PageInfo.getTotal(), spuBos);
}
Mybatis-Plus(简称MP)是一个 Mybatis 的增强工具,在 Mybatis 的基础上只做增强不做改变,为简化开发、提高效率而生。这是官方给的定义,关于mybatis-plus 的更多介绍及特性,可以参考 mybatis-plus 官网。那么它是怎么增强的呢?其实就是它已经封装好了一些 crud方 法,我们不需要再写 xml 了,直接调用这些方法就行,就类似于 JPA。参考地址