一、框架的作用
用来代替jdbc链接数据库的一个框架,主要是进行对数据库数据的操作,优点有:易于学习和使用,只需要通过xml的配置,在xml文件里写sql语句,然后可以自己定义名字,通过xml的映射来将数据库的操作映射到后端java代码中。
二、使用普通方法实现
1、xml文件链接数据库的配置
首先Mybatis框架需要先链接数据库,创建一个SqlConfig.xml文件,写上以下代码链接数据库
<configuration>
<!-- 使用mybatis需要的数据源和事务配置-->
<environments default="development">
<!-- 配置数据源和事务 -->
<environment id="development">
<!-- 配置事务管理,将事务管理交给mybatis管理 -->
<transactionManager type="JDBC" />
<!-- 配置数据源 -->
<dataSource type="POOLED">
<property name = "driver" value = "com.mysql.jdbc.Driver" />
<!-- 链接数据库的名字 -->
<property name="url" value="jdbc:mysql://localhost:3306/db_database?useUnicode=true&characterEncoding=utf-8"/>
<!-- 链接数据库的用户名 -->
<property name="username" value="root"/>
<!-- 链接数据库的密码 -->
<property name="password" value=""/>
</dataSource>
</environment>
</environments>
</configuration>
2、编写失血模型
失血模型就是只有属性,没有任何的逻辑,主要是用户与数据库信息交换的载体,所以字段名最好和数据库里面的字段名一致。以数据库里面的t_business表格为例:
@Data
public class Business {
private String business_id;
private String business_name;
private String password;
private String tel;
private String real_name;
private String id_card;
private String status;
private Date open_time;
private String address;
}
3、编写单个表格的映射关系
为了实现sql数据库操作语句与java代码的耦合,所以需要再编写一个business.xml文件映射。
<!-- namespace:命名空间,可以理解成将部分的sql语句进行隔离。-->
<mapper namespace="test">
<!-- Select表示查询标签,里面放查询语句,id表示表示这个查询标签的名字
parameterType表示输入参数的数据类型,resultType表示输出参数的数据类型,一般绑定成model的对象
#{value}:表示用来和占位符一样,用来接受输入的参数值。-->
<select id="findById" parameterType="java.lang.Integer" resultType="com.model.Business">
SELECT * FROM t_business WHERE business_id = #{value}
</select>
<!-- 修改方法的代码编写,主要除了查询以外,修改,新增,删除不需要用到 resultType标签 -->
<update id="updateProduct" parameterType="com.model.Business">
UPDATE t_business SET name = #{name} WHERE business_id = #{p_id}
</update>
<!-- 新增方法的代码编写 -->
<insert id="insertProduct" parameterType="com.woniuxy.model.Product">
INSERT INTO t_business(business_name,password,tel) value(#{name},#{password},#{tel});
</insert>
<!-- 删除方法的代码编写 -->
<delete id="deleteProduct" parameterType="java.lang.Integer">
DELETE FROM t_business WHERE business_id = #{value}
</delete>
</mapper>
编写完成以后,需要在SqlConfig.xml文件进行引用,否则没有效果
引用位置如下:
<!-- 加载business..xml配置文件 -->
<mappers>
<mapper resource="business.xml"/>
</mappers>
4、最后创建会话工厂运行代码
public class MybatisDemo1 {
public static void main(String[] args) throws IOException {
//1、读取到SqlConfig.xml文件的流
String path = "SqlConfig.xml";
InputStream config = Resources.getResourceAsStream(path);
//创建会话工厂,同时将SqlMapConfig.xml里面的数据放到工厂中
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(config);
//开启会话
SqlSession sqlSession = factory.openSession();
//第一个参数:要执行哪个sql语句的参数,一般命名空间.id值
//第二个参数:传入填充#{value}的传入参数值。
Business business = sqlSession.selectOne("test.findById", 1);
System.out.println(business);
}
}
三、使用Mapper代理模式实现
第一步:同样是创建SqlConfig.xml文件链接数据库,代码同上<br /> 代理模式mapper映射的配置如下,通过class标签实现<br />要求:<br />对应的mapper.java接口文件和mapper.xml配置文件必须放在同一个包下面。<br /> mapper.java接口文件和mapper.xml配置文件的名字必须要一致。
<mappers>
<!-- class:对应接口的全路径名 -->
<mapper class="com.mapper.BusinessDao"/>
<!--以扫描包的形式进行配置更加的方便,推荐使用 name:mapper接口的包名 -->
<package name="com.mapper"/>
</mappers>
第二步:创建失血模型Business.java文件,代码同上
第三步:创建mapper接口进行代理
代码如下:
package com.mappers;
public interface BusinessDao {
//商家登录
public Business businessLogin(String tel,String password);
}
第四步:创建一个business.xml文件进行映射,不同于上面的xml文件,这次的xml文件有一些规则
<!-- namespeace必须写成对应的Mapper接口的全路径名 -->
<mapper namespace="com.mappers.BusinessDao">
<!-- id必须和接口中对应方法的名称一致。
parameterType:必须要和接口中方法的形式参数数据类型一致。
resultType:必须和接口中方法的返回值数据类型一致。-->
<select id="businessLogin" parameterType="java.lang.Integer" resultType="com.model.Business">
SELECT * FROM t_business WHERE tel = #{tel} AND password=#{password}
</select>
</mapper>
第五步:创建会话工厂后继承接口并使用代码模式调用xml文件sql语句
public class BusinessDaoimpl implements BusinessDao {
private SqlSession session;
public SqlSession Until() {
try {
InputStream config=Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(config);
session=factory.openSession();
} catch (IOException e) {
e.printStackTrace();
}
return session;
}
@Override
//继承后重写商家登录的方法
public Business businessLogin(String tel,String password){
//通过getMapper方法实现代理
BusinessDao businessdao=Until().getMapper(BusinessDao.class);
Business business=businessdao.businessLogin(tel, password);
return business;
}
}
四、关于xml文件配置的一些简化方法
1、通过SqlConfig.xml文件使用别名进行映射
因为开发过程中,比如parameterType标签里或者resultType标签里总是要吧链接写完整,非常的麻烦,而且还是多次被重复使用,可以选择给该路径取一个别名。用别名来替换比较复杂的全路径名。
配置位置如下:
<!-- 设置一个别名 -->
<typeAliases>
<!-- type: 要设置的model全路径名 alias:取的别名 -->
<typeAlias type="com.model.Business" alias="business"/>
<!-- package标签更为简便,推荐使用批量设置别名,扫描model包,默认的别名是该java类的类名-->
<package name="com.model"/>
</typeAliases>
五、自定义POJO类
有些时候普通的失血模型已经无法满足查询输入参数的要求了,这时候我们需要自己定义一个失血模型类,比如同时查询用户与用户购物车之间的关系。<br /> 第一步:重新创建一个失血模型,包含有User类和Cart类
public class UserCart {
private User user;
private Cart cart;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Cart getCart() {
return userInfo;
}
public void setCart(Cart cart) {
this.cart = cart;
}
@Override
public String toString() {
return "UserCustom [user=" + user + ", cart=" + cart + "]";
}
}
第二步:编写UserCart.xml文件
<!-- 根据t_user表的id和t_cart表的user_id关系查询出想要的user对象的值 -->
<select id="queryUserCart" parameterType="UserCart" resultType="User">
SELECT * FROM t_user as s1 JOIN t_Cart as s2 on s1.user_id = s2.user_id
</select>
但是这样的方法不能够完全查询出符合条件的所有数据,只有t_user表里面的数据或者t_cart表里面的数据。不推荐
六、mybatis里的输入映射和输出映射
输入映射:目前在mybatis用到的都是parameterType类型,其中可以是简单数据类型,也可以是包装类型(POJO)。
输出映射:
1、输出的类型为POJO包装类(model失血模型),用的是resultType
2、输出的类型为简单数据类型,resultType,要求:查询的结果集必须是一行一列的数据
3、输出类型为resultMap方式,当前的作用,当java中失血模型对象中的字段和数据库中的字段名称不一致的时候,可以使用resultMap方式进行自定义映射关系。
例子:
<resultMap type="User" id="userResult">
<!-- id: 用来关联在查询结果集中可以唯一识别一个实体的字段,一般都是主键
column:在查询结果集中,能够唯一识别一个实体的字段名
property:结果集中的数据和失血模型中对应的属性名。
-->
<id column="id" property="user_id"/>
<!--
result:除了能唯一识别实体字段以外的其他普通字段
column:在查询结果集中,数据库的字段名
property:结果集中的数据和失血模型中对应的属性名。
-->
<result column="userName" property="user_name"/>
<result column="pass" property="password"/>
</resultMap>
<!-- 查询所有用户 -->
<select id="queryAllUser" resultMap="userResult">
SELECT user_id id,user_name userName,`password` pass FROM t_user
</select>
七、一对多、多对一的完整查询输出
通过自定义POJO类和resultMap映射来实现一对多或者多对一的查询为了实现在一对多关系中查询出的数据进行全部的显示出来。
resultType和resultMap区别
resultType:会增加一个扩展的模型类,但是,这样可以简化xml配置文件中的繁琐步骤。操作和单表查询操作一致,一对一的映射关系一般推荐使用resultType。
resultMap:需要有关联映射,需要自定义resultMap的配置信息,增加配置的难度,但是,可以用在一对多的关系映射中(较为复杂的关系映射)。
1、一对多查询
需求:查询订单和用户及订单明细
主表:订单
附表:用户、订单明细 自定义POJO类
将订单明细定义成列表,作为属性在Order模型中声明。
private List detailList;
mapper.java接口
//查询订单和用户及订单明细信息
public List queryOrderAndUserAndDetail() throws Exception;
mapper.xml配置文件
<!-- 自定义resultMap -->
<resultMap type="Order" id="OrderUserDetailResult">
<id column="id" property="id"/>
<result column="user_id" property="user_id"/>
<result column="order_create_time" property="create_time"/>
<result column="note" property="note"/>
<!-- 关联用户 -->
<association property="user" javaType="User">
<id column="user_id" property="id"/>
<result column="user_name" property="user_name"/>
<result column="birthday" property="birthday"/>
<result column="gender" property="gender"/>
<result column="address" property="address"/>
</association>
<!-- 关联订单明细:列表,一对多的关系 -->
<!--
collection:关联映射一对多的关系。属性是集合类型声明的
property:关联在Order表中的属性名称
-->
<collection property="detailList" ofType="Detail">
<id column="detail_id" property="id"/>
<result column="product_id" property="product_id"/>
<result column="detail_create_time" property="create_time"/>
<result column="items_id" property="items_id"/>
<result column="number" property="number"/>
</collection>
</resultMap>
<!-- 查询订单和用户及订单明细信息 -->
<select id="queryOrderAndUserAndDetail" resultMap="OrderUserDetailResult">
SELECT
o.id,
o.user_id,
o.create_time AS order_create_time,
o.note,
d.id AS detail_id,
d.product_id,
d.create_time AS detail_create_time,
d.items_id,
d.number,
u.user_name,
u.birthday,
u.gender,
u.address
FROM
t_order AS o JOIN t_detail AS d ON o.id = d.order_id
LEFT JOIN
t_user AS u ON o.user_id = u.id;
</select>
2、多对多关系映射
需求:查询用户购买了哪些商品订单。
- 在User模型中添加List orderList;一个用户可以拥有多个订单。
- 在Order模型中添加List detailList;一个订单可能有多个订单明细。
- 在Detail模型中添加Product product;一个订单明细只能对应一个商品数据。
mapper.java接口
//查询用户购买的商品详细信息
public List<User> queryUserProduct() throws Exception;
mapper.xml配置文件
<!-- 自定义resultMap -->
<resultMap type="User" id="userAndProduct">
<!-- 主表的映射关系 -->
<id column="id" property="id"/>
<result column="user_name" property="user_name"/>
<result column="birthday" property="birthday"/>
<result column="gender" property="gender"/>
<result column="address" property="address"/>
<!-- 配置order映射关系 -->
<collection property="orderList" ofType="Order">
<id column="order_id" property="id"/>
<result column="order_create_time" property="create_time"/>
<result column="note" property="note"/>
<!-- 配置detail映射关系 -->
<collection property="detailList" ofType="Detail">
<id column="detail_id" property="id"/>
<result column="product_id" property="product_id"/>
<result column="detail_create_time" property="create_time"/>
<result column="items_id" property="items_id"/>
<result column="number" property="number"/>
<!-- 配置product映射关系 -->
<association property="product" javaType="Product">
<id column="product_id" property="id"/>
<result column="product_name" property="product_name"/>
<result column="add_time" property="add_time"/>测试方法
<result column="total" property="number"/>
<result column="price" property="price"/>
</association>
</collection>
</collection>
</resultMap>
<!-- 查询用户的所有购买的商品及订单信息 -->
<select id="queryUserProduct" resultMap="userAndProduct">
SELECT
u.id,
u.user_name,
u.birthday,
u.gender,
u.address,
o.id AS order_id,
o.create_time AS order_create_time,
o.note,
d.id AS detail_id,
d.product_id,
d.create_time AS detail_create_time,
d.items_id,
d.number,
p.product_name,
p.number AS total,
p.add_time,
p.price
FROM
t_user AS u JOIN t_order AS o ON u.id = o.user_id
JOIN
t_detail AS d ON o.id = d.order_id
JOIN
t_product AS p ON d.product_id = p.id;
</select>
八、动态sql
1、if 和 where 标签和include标签
if标签中可以判断传入的值是否符合某种规则,比如是否不为空;
where标签可以用来做动态拼接查询条件,当和if标签配合的时候,不用显示的声明类似where 1=1这种无用的条件,来达到匹配的时候and会多余的情况;
include可以把大量重复的代码整理起来,当使用的时候直接include即可,减少重复代码的编写;
<!-- 编写一个sql片段,供以后重复使用sql片段 -->
<sql id="userInfo">
<if test="user.user_name != null">
AND s1.user_name like "%${user.user_name}%"
</if>
<if test="userInfo.gender != null">
AND s2.gender = #{userInfo.gender}
</if>
</sql>
<!--动态Sql : where / if-->
<select id="findUserById" resultType="com.lagou.pojo.User">
select * from user
<where>
<include refid="userInfo"/>
</where>
</select>
choose、when、otherwise 标签
类似于 Java 中的 switch、case、default。只有一个条件生效,也就是只执行满足的条件 when,没有满足的条件就执行 otherwise,表示默认条件
<!--动态Sql: choose、when、otherwise 标签-->
<select id="findUserById" resultType="com.lagou.pojo.User">
select * from user
<where>
<choose>
<when test="name != null and name != ''">
AND name = #{name}
</when>
<otherwise>
AND id = #{id}
</otherwise>
</choose>
</where>
</select>
2、foreach 标签
foreach标签可以把传入的集合对象进行遍历,然后把每一项的内容作为参数传到sql语句中,里面涉及到有item,index,collection,open,separator,close。
(1)item(具体的每一个对象)表示集合中每一个元素进行迭代时的别名。
(2)index(序号)指定一个名字,用于表示在迭代过程中,每次迭代到的位置。
(3)open(开始符),表示该语句以什么开始。
(4)close(结束符),表示以什么结束。
(5)separator(分隔符),表示在每次进行迭代之间以什么符号作为分隔符。
在使用foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的,主要有一下3种情况:
(1)如果传入的是单参数且参数类型是一个List的时候,collection属性值为list
(2)如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array
(3)如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了,当然单参数也可以封装成map,实际上如果你在传入参数的时候,在MyBatis里面也是会把它封装成一个Map的,map的key就是参数名,所以这个时候collection属性值就是传入的List或array对象在自己封装的map里面的key。
<!--动态Sql: foreach标签, 批量插入-->
<insert id="insertBatch" useGeneratedKeys="true" keyProperty="id">
insert into user (id, name)
values
<foreach collection="list" item="user" separator="," >
(#{user.id}, #{user.name})
</foreach>
</insert>
<!--动态Sql: foreach标签, in查询-->
<select id="dynamicSqlSelectList" resultType="com.lagou.pojo.User">
SELECT * from user WHERE id in
<foreach collection="list" item="id" open="(" close=")" separator="," >
#{id}
</foreach>
</select>
map参数
<!--动态Sql: foreach标签, map参数查询-->
<select id="findByMap" resultType="com.lagou.pojo.User">
select * from user WHERE
<foreach collection="map" index="key" item="value" separator="=">
${key} = #{value}
</foreach>
</select>
set标签
适用于更新中,当匹配某个条件后,才会对该字段进行更新操作
<!--动态Sql: set 标签-->
<update id="updateSet" parameterType="com.lagou.pojo.User">
update user
<set>
<if test="name != null and name != ''">
name = #{name},
</if>
</set>
where id = #{id}
</update>
3、trim标签
是一个格式化标签,主要有4个参数:
prefix(前缀)
prefixOverrides(去掉第一个标记)
suffix(后缀)
suffixOverrides(去掉最后一个标记)
<!--动态Sql: trim 标签-->
<select id="findUser" resultType="com.lagou.pojo.User">
select * from user
<trim prefix="where" suffix="order by id" prefixOverrides="and | or" suffixOverrides=",">
<if test="name != null and name != ''">
AND name = #{name}
</if>
<if test="id != null">
AND id = #{id}
</if>
</trim>
</select>
十、mybatis中输入映射和输出映射(重点)
1、输入映射:
目前在mybatis用到的都是parameterType类型,其中可以是简单数据类型,也可以是包装
类型(POJO)
parameterType:java.lang.Integer\java.lang.String\POJO模型类。
2、输出映射
- 输出的类型为POJO包装类(model失血模型),用的是resultType
- 输出的类型为简单数据类型,resultType,要求:查询的结果集必须是一行一列的数据;
- 输出类型为resultMap方式,当前的作用,当java中失血模型对象中的字段和数据库中的字段名称 不一致的时候,可以使用resultMap方式进行自定义映射关系。
CDATA区(数据区):不管xml当中是否用标签标记,如果写在CDATA区,那么浏览器都会当成普通数据处理 特殊字符:转义字符 ```xml <![CDATA[<resultMap type="User" id="userResult"> <!-- id: 用来关联在查询结果集中可以唯一识别一个实体的字段,一般都是主键 column:在查询结果集中,能够唯一识别一个实体的字段名 property:结果集中的数据和失血模型中对应的属性名。 --> <id column="id" property="user_id"/> <!-- result:除了能唯一识别实体字段以外的其他普通字段 column:在查询结果集中,数据库的字段名 property:结果集中的数据和失血模型中对应的属性名。 --> <result column="userName" property="user_name"/> <result column="pass" property="password"/> </resultMap> <!-- 查询所有用户 --> <select id="queryAllUser" resultMap="userResult"> SELECT user_id id,user_name userName,`password` pass FROM t_user </select>
]]>任何数据
<a name="2Cc1t"></a>
## 十一、延迟加载、一级缓存和二级缓存(重点)
<a name="9Yas6"></a>
#### 1、延迟加载:
第一步:
```xml
<select id="OrdersUserLazyLoading" resultMap="findOrdersUserLazyLoading">
select * from orders
</select>
<!--编写延迟加载resultMap-->
<resultMap id="findOrdersUserLazyLoading" type="orders">
<!--编写订单映射-->
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!-- 实现对用户信息进行延迟加载
select:指定延迟加载需要执行的statement的id(是根据user_id查询用户信息的statement)
要使用userMapper.xml中findUserById完成根据用户id(user_id)用户信息的查询,
如果findUserById不在本mapper中需要前边加namespace
column:订单信息中关联用户信息查询的列,是user_id(外键关联这个user表的id)
关联查询的sql理解为:
SELECT orders.*,
(SELECT username FROM USER WHERE orders.user_id = user.id)username,
(SELECT sex FROM USER WHERE orders.user_id = user.id)sex
FROM orders
-->
<association property="user" javaType="user"
select="com.lfm.mapper.UserMapper.findUserById" column="user_id">
<!-- 实现对用户信息进行延迟加载 -->
</association>
</resultMap>
第二步:
去SqlMapConfig中配置打开延迟加载(默认是不开启的)
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
1.一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
2.二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
一级缓存(默认打开的)
如果在两次查询操作之间发生了数据修改呢?没关系,mybatis有个刷新缓存机制,会保持缓存中的数据都是最新的,我们可以测试一下,在两次查询操作中将id=1的用户更新了第二次再查询;
2、二级缓存需要手动开启
1、开启二级缓存
<setting name="cacheEnable" value="true" />
2、还要在想要使用二级缓存的mapper.xml文件中开启二级缓存,这样整个mapper开算是开启了二级缓存
<mapper namespace="com.xxx.xxx">
<cache/>
</mapper>
注意:
select语句中设置useCache = “false”将不被缓存;
语句中设置flushCache = “false”将不刷新缓存;
pojo类必须是可序列化类。