MVC模型
Model模型:JavaBean实现,用于封装业务数据UserBean和业务逻辑UserDaoImpl
View视图:JSP+JSTL实现,用于收集客户动作<form>
和显示业务数据<table>
Controller控制器:Servlet实现,用于流程控制
MyBatis开发步骤
1、添加依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2、定义核心配置文件 resources/mybatis-config.xml
<?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>
<properties resource="database.properties"/>
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
</configuration>
3、创建表
create table t_users(
id bigint primary key auto_increment,
username varchar(20) not null unique,
password varchar(20) not null
)engine=innodb default charset utf8;
根据表结构定义对应的实体类
@Data
public class UserBean implements Serializable {
private Long id;
private String username;
private String password;
}
定义对应的映射元文件 resources/com/yan/mapper/UserBean-mapper.xml
<?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>
<resultMap id="baseResultMapper" type="com.yan.entity.UserBean" autoMapping="true">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="username" property="username" jdbcType="VARCHAR"/>
<result column="password" property="password" jdbcType="VARCHAR"/>
</resultMap>
<insert id="save" parameterType="com.yan.entity.UserBean" useGeneratedKeys="true" keyProperty="id">
insert into t_users(username,password) values(#{username},#{password})
</insert>
</mapper>
jdbcType的设置名称对应的是java.jdbc.Types类中的常量名称
定义映射文件后需要在核心配置文件中进行注册 mybatis-config.xml
<mappers>
<mapper resource="com/yan/mapper/UserBean-mapper.xml"/>
</mappers>
目前一般建议使用Mapper接口的方式进行调用,所以需要添加对应的Mapper接口
public interface UserMapper {
//对应映射元文件中的insert\update\delete\select标签
int save(UserBean user);
}
修改映射元文件的名空间和Mapper接口的全名一致
<mapper namespace="com.yan.mapper.UserMapper">
4、定义工具类
重点:4大核心组件的生命周期
public class MybatisSessionFactory {
private MybatisSessionFactory(){}
private static SqlSessionFactory factory=null;
private static final ThreadLocal<SqlSession> sessions=new ThreadLocal<>();
SqlSession是java.sql.Connection对象的浅封装,必须保证及时关闭
public class OpenSessionInViewFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
try{
chain.doFilter(request, response);
MybatisSessionFactory.commitTransaction();
} catch (Exception e){
MybatisSessionFactory.rollbackTransaction();
throw new ServletException(e);
}finally {
MybatisSessionFactory.closeSession();
}
}
}
Filter的配置信息 web.xml
<filter>
<filter-name>OpenSessionInView</filter-name>
<filter-class>com.yan.filters.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>OpenSessionInView</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
多表关联的问题
对一查询
学生和班级【产品和类目】
create table t_catalog(
id bigint primary key auto_increment,
title varchar(32) not null
)engine=innodb default charset utf8;
create table t_produce(
id bigint primary key auto_increment,
name varchar(32) not null,
catalog_id bigint,
foreign key(catalog_id) references t_catalog(id) on delete cascade
)engine=innodb default charset utf8;
定义实体类
按照业务分析,可以发现获取类目信息时不需要加载对应类型的产品信息,所以这是一个单向关联关系
public class CatalogBean implements Serializable{
private Long id;
private String title;
}
多方的类定义,需要获取一方的信息
public class ProduceBean implements Serializable{
private Long id;
private String name;
private CatalogBean catalog=new CatalogBean();
}
定义映射元文件
如何实现获取产品信息时同时获取对应的类目信息?
写法1:使用级联属性定义
<?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.yan.mapper.ProduceMapper">
<resultMap id="baseResultMap" type="com.yan.entity.ProduceBean">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="name" property="name" jdbcType="VARCHAR"/>
<result column="catalog_id" property="catalog.id" jdbcType="BIGINT"/>
<result column="title" property="catalog.title" jdbcType="VARCHAR"/>
</resultMap>
<select id="loadById" parameterType="long" resultMap="baseResultMap">
select p.*,c.title from t_produce p left join t_catalog c on p.catalog_id=c.id where p.id=#{id}
</select>
</mapper>
写法2:推荐使用
<resultMap id="baseResultMap" type="com.yan.entity.ProduceBean">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="name" property="name" jdbcType="VARCHAR"/>
<association property="catalog" javaType="com.yan.entity.CatalogBean">
<id column="catalog_id" property="id" jdbcType="BIGINT"/>
<result column="title" property="title" jdbcType="VARCHAR"/>
</association>
</resultMap>
<select id="loadById" parameterType="long" resultMap="baseResultMap">
select p.*,c.title from t_produce p left join t_catalog c on p.catalog_id=c.id where p.id=#{id}
</select>
写法3:不推荐使用
<resultMap id="baseResultMap" type="com.yan.entity.ProduceBean">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="name" property="name" jdbcType="VARCHAR"/>
<association property="catalog" select="com.yan.mapper.CatalogMapper.loadById" column="catalog_id"/>
</resultMap>
<select id="loadById" parameterType="long" resultMap="baseResultMap">
select * from t_produce where id=#{id}
</select>
###association对一
-
select:另一个映射查询的id,MyBatis会额外执行这个查询获取嵌套对象的结果。
-
column:将主查询中列的结果作为嵌套查询的参数,配置方式如column="{prop1=col1,prop2=col2}",prop1和prop2将作为嵌套查询的参数。
-
fetchType:数据加载方式,可选值为lazy和eager分别为延迟加载和积极加载。
-
如果要使用延迟加载,除了将fetchType设置为lazy,还需要注意全局配置aggressiveLazyLoading的值应该为false。这个参数在3.4.5版本之前默认值为ture,从3.4.5版本开始默认值改为false
对多查询
获取订单信息时需要获取对应的订单详情信息
create table t_order(
id bigint primary key auto_increment
order_date timestamp default current_timestamp,
all_price numeric(8,2)
)engine=innodb default charset utf8;
create table t_order_item(
id bigint primary key auto_increment,
produce_id bigint not null,
price numeric(8,2),
num int default 1,
order_id bigint not null,
foreign key(order_id) references t_order(id) on delete cascade
)engine=innodb default charset utf8;
实体类定义
一般查询订单详细信息时,需要加载对应的订单详情信息
public class OrderBean implements Serializable{
private Long id;
private Date orderDate;
private Double allPrice;
private Set<OrderItemBean> items=new HashSet<>(0); //0这个参数实际上含义在于避免不存储数据但是开辟空间,实际上JDK1.8中HashMap无参构建时已经使用了延迟初始化数组的处理,所以0不是必须的
}
订单详情类
public class OrderItemBean implements Serializable{
private Long id;
private Long produceId;
private Double price;
private Integer num;
}
定义映射元文件
如何实现获取订单信息时同时获取对应的订单详情信息?
方法1:
<mapper namespace="com.yan.mapper.OrderMapper">
<resultMap id="baseResultMap" type="com.yan.entity.OrderBean">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="order_date" property="orderDate" jdbcType="TIMESTAMP"/>
<result column="all_price" property="allPrice" jdbcType="NUMERIC"/>
</resultMap>
<resultMap id="extResultMap" extends="baseResultMap" type="com.yan.entity.OrderBean">
<collection property="items" ofType="com.yan.entity.OrderItemBean" select="selectByOrderId" column="id"/>
</resultMap>
<select id="loadById" resultMap="extResultMap" parameterType="long">
select * from t_order where id=#{id}
</select>
<select id="selectAll" resultMap="baseResultMap">
select * from t_order
</select>
<select id="selectByOrderId" resultType="com.yan.entity.OrderItemBean">
select * from t_order_item where order_id=#{id}
</select>
</mapper>
修改接口定义
public interface OrderMapper {
OrderBean loadById(Long id);
List<OrderItemBean> selectByOrderId(long id);
}
方法2:
<resultMap id="baseResultMap" type="com.yan.entity.OrderBean">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="order_date" property="orderDate" jdbcType="TIMESTAMP"/>
<result column="all_price" property="allPrice" jdbcType="NUMERIC"/>
</resultMap>
<resultMap id="extResultMap" extends="baseResultMap" type="com.yan.entity.OrderBean">
<collection property="items" ofType="com.yan.entity.OrderItemBean">
<id column="item_id" property="id" jdbcType="BIGINT"/>
<result column="produce_id" property="produceId" jdbcType="BIGINT"/>
<result column="num" property="num" jdbcType="NUMERIC"/>
</collection>
</resultMap>
<select id="loadById" resultMap="extResultMap" parameterType="long">
select od.*,it.id item_id,it.produce_id,it.num from t_order od left join t_order_item it on od.id=it.order_id where od.id=#{id}
</select>
###collection对多
-
select:另一个映射查询的id,MyBatis会额外执行这个查询获取嵌套对象的结果。
-
column:将主查询中列的结果作为嵌套查询的参数,配置方式如column="{prop1=col1,prop2=col2}",prop1和prop2将作为嵌套查询的参数。
-
fetchType:数据加载方式,可选值为lazy和eager,分别为延迟加载和积极加载。
-
如果要使用延迟加载,除了将fetchType设置为lazy,还需要注意全局配置aggressiveLazyLoading的值应该为false
##继承在表中的实现
继承的实现方法
将一个继承树中的所有内容都存储在一个表中,引入一个特殊的标识列用于标识当前数据的具体子类型
create table t_vehicle(
id bigint primary key auto_increment,
name varchar(32) not null, -- 公共属性 父类
door_num int, -- Car子类型中的特有属性
driven_num int, -- Suv子类型中的特有属性
_type int default 0 -- 标识列,用于说明当前行数据的具体类型0标识Vehicle,1Car2Suv
)engine=innodb default charset utf8;
实体类
父类
public class Vehicle{
private Long id;
private String name;
}
子类型1 -Car
public class Car extends Vehicle{
private int doorNum;
}
子类型2 -Suv
public class Suv extends Vehicle{
private int drivenNum;
}
对应的映射配置文件
<resultMap id="VehicleMap" type="com.yan.entity.VehicleBean">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="name" property="name" jdbcType="VARCHAR"/>
<discriminator javaType="int" column="_type">
<case value="1" resultType="com.yan.entity.CarBean">
<result property="doorNum" column="door_num"/>
</case>
<case value="2" resultType="com.yan.entity.SuvBean">
<result property="drivenNum" column="driven_num"/>
</case>
</discriminator>
</resultMap>
<select id="loadById" parameterType="long" resultMap="VehicleMap">
select * from t_vehicle where id=#{id}
</select>
动态SQL
if标签用于执行ONGL表达式判断
动态查询
<select id="selectByMap" parameterType="map" resultMap="baseResultMapper">
select * from t_users where 1=1
<if test="username!=null">
and username=#{username}
</if>
<if test="password!=null">
and password=#{password}
</if>
</select>
整形范围查询 between/and
<select id="selectByMap2" parameterType="map" resultMap="baseResultMapper">
select * from t_users where id between #{beginId} and #{endId}
</select>
字符串模糊查询
<select id="selectByMap" parameterType="map" resultMap="baseResultMapper">
select * from t_users where 1=1
<if test="username!=null">
and username like #{username}
</if>
</select>
是否使用模糊查询取决于传递的参数值username中是否有通配符 _或者%
另外写法:
<select id="selectLike" parameterType="map" resultMap="baseResultMapper">
select * from t_users where username like concat('%',#{username},'%')
</select>
动态插入
trim标签可以在语句体的开头或者末尾添加执行的内容或者剔除指定的内容
- prefix在语句体的头部添加指定内容,suffix在语句体的末尾添加指定内容
- prefixoverrides在语句体的头部去除指定内容,suffixoverriders在语句体的末尾去除指定内容
<insert id="save" parameterType="User">
insert into t_users(
username,password,
<trim suffixOverrides=",">
<if test="birth!=null">birth,</if>
<if test="sex!=null">sex,</if>
</trim>
) values(
#{username},#{password},
<trim suffixOverrides=",">
<if test="birth!=null">#{birth,jdbcType=DATE},</if>
<if test="sex!=null">#{sex},</if>
</trim>
)
</insert>
如果表中某些列不允许为null
<insert id="save" parameterType="User">
insert into t_users(
username,password
<if test="birth!=null">,birth</if>
<if test="sex!=null">,sex</if>
) values(
#{username},#{password}
<if test="birth!=null">,#{birth,jdbcType=DATE}</if>
<if test="sex!=null">,#{sex}</if>
)
</insert>
动态修改
<update id="updateUser" parameterType="User">
update t_users set id=#{id}
<if test="username!=null">
,username=#{username}
</if>
<if test="password!=null">
,password=#{password}
</if>
where id=#{id}
</update>
set 用法
如果该标签包含的元素中有返回值,就插入一个set,如果set后面的字符串是以,结尾的,就将这个逗号剔除。
需求:更新用户信息的时候不能将原来有值但没有发生变化的字段更新为空或null,即只更新有值的字段。
<update id="updateUser" parameterType="User">
update t_users
<set>
<if test="username!=null">
username=#{username},
</if>
<if test="password!=null">
password=#{password},
</if>
</set>
where id=#{id}
</update>
where 用法
如果该标签包含的元素中有返回值,就插入一个where,如果where后面的字符串是以AND或者OR开头的,就将它们剔除
需求:根据用户的输入条件来查询用户列表,如果输入了用户名,就根据用户名模糊查询,如果输入了邮箱,就根据邮箱精确查询,如果同时输入了用户名和邮箱,就用这两个条件去匹配用户
<select id="selectByUserWhere" resultType="com.yan.entity.UserBean">
SELECT * FROM t_users
<where>
<if test="userName != null and userName != ''">
AND user_name LIKE CONCAT('%',#{userName},'%')
</if>
<if test="userEmail != null and userEmail != ''">
AND user_email = #{userEmail}
</if>
</where>
</select>
choose用法
当参数id有值时优先使用id查询,当id没有值时就去判断用户名是否有值,如果有值就用用户名查询,如果没值,就使查询无结果
<select id="selectByIdOrUserName" resultType="com.yan.entity.SysUser">
SELECT * FROM sys_user WHERE 1 = 1
<choose>
<when test="id != null">
AND id = #{id}
</when>
<when test="userName != null and userName != ''">
AND user_name = #{userName}
</when>
<otherwise>
AND 1 = 2
</otherwise>
</choose>
</select>
如果没有otherwise这个限制条件,当id和userName都为null时,所有的用户都会被查询出来,但我们接口的返回值是SysUser,当查询结果是多个时就会报错。添加otherwise条件后,由于where条件不满足,因此在这种情况下就查询不到结果。
因为在标签中没有if/else结构,所以可以使用choose模拟出if/else
需求:如果id非空则按照id查询,否则按照id为null进行查询
<select if="select1" parameterType="com.yan.entity.UserBean" resultType="com.yan.entity.UesrBean">
select * from t_users
<choose>
<when test="id!=null">
where id=#{id}
</when>
<otherwise>
where id is null
</otherwise>
</choose>
</select>
foreach用法
主要三种用途:foreach实现in集合、foreach 实现批量插入、foreach 实现动态update
foreach 实现in集合
根据传入的用户id集合查询出所有符合条件的用户,此时需要使用到Sql中的IN,如 id in (1,10,101)
<select id="selectByIdList" resultType="com.zwwhnly.mybatisaction.model.SysUser">
SELECT * FROM sys_user WHERE id IN
<foreach collection="list" open="(" close=")" separator="," item="id" index="i">
#{id}
</foreach>
</select>
- open:整个循环内容开头的字符串。
- close:整个循环内容结尾的字符串。
- separator:每次循环的分隔符。
- item:从迭代对象中取出的每一个值。
- index:如果参数为集合或者数组,该值为当前索引值,如果参数为Map类型时,该值为Map的key。
- collection:要迭代循环的属性名。
foreach 实现批量插入
将传入的用户集合批量写入数据库 insertUserList(List<UserBean> list)
<insert id="insertUserList" parameterType="list">
insert into t_users(username,password) values
<foreach collection="list" item="u" separator=",">
(#{u.username},#{u.password})
</foreach>
</insert>
foreach 实现动态update
传入Map更新数据 int updateByMap(Map<String,Object> map);
<update id="updateByMap" parameterType="map">
UPDATE sys_user SET
<foreach collection="_parameter" item="val" index="key" separator=",">
${key} = #{val}
</foreach>
WHERE id = #{id}
</update>