SSM框架——Mybatis框架

一、配置文件基本应用

1.框架:就是一套规范,按照框架的约束遵守进行开发

底层原理:利用反射技术和拦截器机制获取前端传来的表单的值,使用框架可以直接调用方法。

持久层框架:Mybatis、hibernate、spring jdbc

表现层框架:springMVC框架 struts2框架

全栈框架:spring框架(能在持久层(完成数据库操作)、业务层(接受持久层结果完成业务逻辑)和表现层(接受业务层结果,完成与前端用户交互)都给出解决方案)

2.原始JDBC操作

利用反射机制创建JDBC对象,获取连接,创建Statement,执行操作,对结果集进行封装操作。

存在问题:存在硬编码问题,频繁创建或销毁连接,需要对结果集进行封装

解决方案:将sql存在XML中,使用连接池,利用反射机制等技术将数据库结果自动封装到实体里(Javabean)

3.Mybatis:基于ORM半自动轻量级持久层框架,可实现在只关注sql语句的同时,自动创建,销毁连接,创建Statement,设置sql参数,以及结果集的封装 且Mybatis内有自带的连接池。

ORM(对象关系映射 O:对象模型Javabean  R:关系型数据库  M:从R到O的映射,通过XML文件映射):将数据库查询结果通过XML文件映射到javabean组件中,先让实体类于数据库表对应,再让实体类属性与表中字段对应。

半自动/全自动:取决于是否需要手动编写sql(需要则为半自动)

4.Mybatis开发步骤:

4.1数据库及表的创建

4.2创建Maven工程导入依赖(Mysql,Mybatis,junit)Maven工程与普通java工程区别在于:Maven工程通过pom.xml对导入库进行依赖管理,而普通的工程则需要导入相应的jar包进行项目管理

4.3编写实体类

4.4编写映射文件和核心配置文件(XML)

4.4.1核心配置文件sqlmapConfig.xml(resource目录下):

 <typeAliases>标签:在<configuration>标签下单独设置,可实现单个实体类别名设置,也可集体配置,配置别名后,在映射文件中就可以直接用别名代替全名。

 <configuration>

        <typeAliases>

                1.单个实体类起别名

                <typeAlias type="com.example.User" alias="User" />  

                2.批量起别名,将domain包下所有文件进行扫描,别名就是包名不区分大小写

                <package name="com.example.domain" />                                             

        </typeAliases>

</configuration>

<environments>:配置Mybatis的环境,一般可配置多个,DEFAULT字段来决定默认使用何种环境

<environment>:具体的环境配置,ID代表该环境的名称,只是一个标识而已

<transactationmanager>:使用何种事务管理器管理数据库,就是用什么工具可以通过Java操纵数据库。JDBC代表使用了JDBC的提交和回滚设置,而MANAGED代表什么都没做而是让容器管理事务的生命周期例如Mybatis与spring整合后事务交给spring容器管理。

<DataSource>:是否使用连接池,Mybatis自己实现的连接池,通过property属性实现连接池的配置信息

<properties>标签:可以将property等配置信息写在一个properties类型文件中,该文件放入resource目

录中

<Mappers>:加载映射配置,核心配置文件中的 <mapper> 标签中resource参数(相对于类路径的资源引用)用于指定映射文件的位置,从而让 MyBatis 能够加载映射文件,并与对应的 Mapper 接口建立关联,不然找不到映射文件。

4.4.2映射文件(resource目录下,且该位置与相对于的类实体的路径也相同,例如user路径为src/com/lagou/pojo/user.java,则Mapper文件则在resource目录下也创建com/lagou/pojo/Usermapper.xml):

子标签:代表执行的sql语句类型:select,insert,update,delete。

参数说明:

namespace和ID共同组成唯一标识,自定义的名称

resultType决定返回值的类型,通过基于ORM利用反射技术等技术将结果封装成具体的实体类

ResultMap:决定返回值类型,当实体类的字段与表中字段名不一致时,先通过resultmap手动封装到实体类再返回resultmap对应的实体类

先在映射类文件中定义resultmap

<resultMap id="userResultMap"(代表该resultmap的标识) type="User"(封装后实体类型可以用别名)>

        <id(代表表中主键字段类型) column="uid" (对应表中的字段)property='id"(对应实体类的属性)></id>

        <result(代表表中普通字段类型) column="Name" property="username"></result>

</resultMap>

parameterType:传入sql的参数数据类型,若传入的参数类型有多种可以用两种方式实现。

第一种:parameterType="java.util.Map",而在调用该方法时提前将结果封装成map:params.put("id", 123); params.put("name", "John");则调用时可通过#{id}和#{name}方式直接获取该参数的值。

 第二种:parameterType="com.pojo.user",也是一个道理传入的是一个类实体对象调用时可通过#{id}和#{name}直接获取该参数的值。传入参数为实体类对象

第三种:无parameterType,直接传参而sql语句直接用#{parameter1}#{parameter2}取代

主键自增获取主键值:

第一种

useGeneratedKeys= "true"和KeyProperty = "实体类中对应的主键属性名":只适用主键自增的数据库,当向数据库插入一条数据时自动返回这条记录的主键值

第二种:更灵活

<insert id="insertUser" useGeneratedKeys="true" keyProperty="id" >

        <selectKey resultType="java.lang.Integer" keyProperty="id" keyColumn=“id” order="AFTER">

                SELECT LAST_INSERT_ID()

        </selectKey>

        INSERT INTO users (username, email) VALUES (#{username}, #{email})

</insert>

参数说明:

resultType:指定主键类型

keyProperty:指定主键封装到实体的ID属性中(实体中的属性)

order:设置在sql之前之前或后 “AFTER”/"BEFORE"

keyColumn:主键列名(表中字段)

SELECT LAST_INSERT_ID()语句代表获取最后一条插入操作生成的自增主键值

4.5进行测试

4.5.1第一种方法:传统开发方式

编写UserMapper接口文件

编写UserMapperImp文件实现该接口:

加载核心配置文件:通过resource工具类从类路径下加载核心配置文件

InputStream is = Resources.getResourceAsStream("核心配置文件名称")

获取sqlsessionFactory工厂对象

SqlSessionFactory sqlsessionFactory  = new SqlSessionFactoryBuilder().build(is)

工厂对象创建sqlsession实例方法

sqlsessionFactory.openSession();手动提交

sqlsessionFactory.openSession(boolean autocommit);自动提交

获取sqlsession会话对象

SqlSession sqlSession = sqlsessionFactory.openSession();

实例方法:传入的参数就是映射文件中的namespace.id 和 传入的参数

selectOne(String statement, Object parameter);查询单个

selectList(String statement, Object parameter)查询所有

int insert(String statement, Object parameter)插入

int update(String statement, Object parameter);更新

int delete(String statement, Object parameter);删除

void commit();提交事务

void rollback():回滚事务

执行sql语句

sqlSession.selectList(namespace.id  映射文件中的唯一标识)

MyBatis会根据映射文件中的SQL语句生成数据库操作语句,并通过JDBC执行SQL语句

Mapper接口和映射文件之间的对应关系是通过核心配置文件中的<mappers>标签或注解实现的。

释放资源

sqlSession.close()

执行代码时:

多态形式创建UserMapper实现类并调用该方法

UserMapper usermapper = new UserMapperImp();

usermapper.findAll();

存在问题:实现类中存在Mybatis代码重复且XML文件中sql语句硬编码到Java代码中

4.5.2第二种方法:代理开发方式

接口代理方式开发:基于接口代理方式动态生成实现类对象,从而只用编写UserMapper接口文件不用编写实现类

规范:

Mapper接口文件的全限定名与Mapper.xml映射文件的namespace相同

Mapper.xml映射文件的Statement的ID 与Mapper接口文件里的方法名相同

Mapper接口文件的方法的参数类型和输出参数类型与mapper.xml映射文件相同

与之前相同,除了核心配置文件要引入映射文件之外还需要接口文件在src目录下的路径和映射文件在resource目录下的路径相同

执行代码时:

InputStream is = Resources.getResourceAsStream("核心配置文件名称")

获取sqlsessionFactory工厂对象

SqlSessionFactory sqlsessionFactory  = new SqlSessionFactoryBuilder().build(is)

获取sqlsession会话对象

SqlSession sqlSession = sqlsessionFactory.openSession();

获得Mapper代理对象

UserMapper usermapper = sqlSession,getMapper(UserMapper.class由接口文件.class做参数)

usermapper.findAll();

这样就不用给Mapper映射文件中的每个方法都写一个实现类文件了,也省去了创建该类对象的过程。一个usermapper就可以调用所有映射文件内的所有方法。

接口代理开发原理:

Mapper接口实现类由代理类MapperProxy代理产生(本质上还是映射文件+接口文件,只是不用写接口文件的实现了而已,自动生成实现类)

该类中有mapperMethod.execute(sqlSession,args)

进入该方法发现该方法工作还是sqlSession工作

sqlSession.insert()等方法

二、配置文件深入理解

1.映射文件深入

1.1.映射文件中模糊查询

当模糊查询时在映射文件的sql语句中使用concat函数完成字符串的拼接

select * from user where username like concat(concat('%',#{username}),'%')

先试用concat('%',#{username})将其拼接一起,再用concat将%username与%拼接在一起

${}与#{}区别:前者可以将传入的内容与sql拼接在一起但并不进行JDBC类型转换会出现sql注入问题但后者可以实现类型转换代表一个占位符;

1.2.映射文件中动态sql之if判断:

传入的参数可能有值也可能没有值,需要判断,若有则加入判断条件,反之则不加入

<select id="getUserList" parameterType="java.util.Map" resultType="User">

        SELECT * FROM users

        <where> (代表where 1=1,若后面的判断条件有一条满足则拼接where标签,反之则不拼)

                 <if test="username != null and username != ''">(若传入参数名为username的值不为空就加入判断)

                         AND username = #{username}

                </if> <if test="email != null and email != ''">

                        AND email = #{email}

                </if>

        </where>

</select>

1.3.动态SQL之<choose>:

choose-when/otherwise:相当于Switch-case/DEFAULT语句

与if类似,但只会选择众多条件中最先满足的条件进行判断,而如果都不满足也会有默认参数进行拼接

<select id="getUserList" parameterType="java.util.Map" resultType="User">
    SELECT * FROM users
    <where>
        <choose>满足第一个就先判断他,其他的不管
            <when test="username != null and username != ''">
                AND username = #{username}
            </when>
            <when test="email != null and email != ''">
                AND email = #{email}
            </when>
            <otherwise>若都不满足则走这个条件,也满足where 1=1 AND status = 'active' 
                AND status = 'active' 
            </otherwise>
        </choose>
    </where>
</select>

1.4.动态sql之set

动态更新数据若对应属性有值则加入更新。反之则不加入

<update id="updateUser" parameterType="User">
    UPDATE users
    <set>
        <!-- 使用 <if> 标签来判断字段是否为空,如果不为空,则添加到 SET 部分 -->
        <if test="username != null">
            , username = #{username}
        </if>
        <if test="email != null">
            , email = #{email}
        </if>
    </set>
    WHERE id = #{id}
</update>
 

1.5.动态sql之foreach标签

用于做数据的遍历

<select id="getUserListByIds" parameterType="java.util.List" resultType="User">
    SELECT * FROM users
    WHERE id IN(类似这种就需要做数据的遍历,将集合内的元素遍历一遍)
    <foreach collection="list" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>
collection:当遍历的是集合则为list/collection,数据则为array,若为类,而该类中的属性为集合类型时则为该类中属性名

item:代表集合内的每个元素赋值给id变量,而该变量在  #{id}中声明出来赋值给sql语句

1.6.SQL片段-include

将重复的SQL字段提取出来已达到重复利用

<!-- 定义 SQL 片段,使用 id 属性给它一个名字 -->
<sql id="selectColumns">
    id, username, email
</sql>

<select id="getUserList" parameterType="java.util.List" resultType="User">
    SELECT <include refid="selectColumns" />重复利用
    FROM users
    WHERE id IN
    <foreach collection="list" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>


2.Mybatis多表查询

2.1多对一/一对一:例如user类中有一个属性为order类实体代表user多对order一

执行顺序:

  1. 首先,执行主表查询(getUserWithProfile)获取用户(User)的基本信息,包括用户的 id、username 和 email。

  2. 然后,根据主表查询结果中的用户 id,执行从表查询(getUserProfileByUserId)来获取该用户的用户资料(UserProfile)的信息,包括资料的 id 和 fullName。

  3. MyBatis 将根据查询结果自动将主表和从表的数据进行关联,构建出包含用户和其用户资料的复合对象。使用 <association> 标签定义了一对一嵌套查询,它表示主表中的 userProfile 属性与从表 UserProfile 实体类进行关联,MyBatis 会根据 <resultMap> 中的映射规则自动将查询结果映射到 UserProfile 实体类,并将其设置到 User 实体类的 userProfile 属性中。(User 类中包含一个 UserProfile)。

类实体的定义:

public class User {
    private Integer id;
    private String username;
    private String email;
    private UserProfile userProfile; // 一对一关系中,User 类包含一个 UserProfile 类的实例

    // 省略构造函数、getters 和 setters 方法
}
 

public class UserProfile {
    private Integer id;
    private String fullName;
    // 其他 UserProfile 类的属性

    // 省略构造函数、getters 和 setters 方法
}
 

resultmap编写:

<resultMap id="UserResultMap" type="User">
    <id property="id" column="user_id" />
    <result property="username" column="username" />
    <result property="email" column="email" />
    <!-- 使用 <association> 标签定义一对一嵌套查询 -->
    <association property="userProfile" resultMap="UserProfileResultMap" />
</resultMap>

<resultMap id="UserProfileResultMap" type="UserProfile">
    <id property="id" column="profile_id" />
    <result property="fullName" column="full_name" />
    <!-- 其他 UserProfile 类的属性映射 -->
</resultMap>

多表查询语句

<select id="getUserWithProfile" resultMap="UserResultMap">
    SELECT u.id AS user_id, u.username, u.email,
           p.id AS profile_id, p.full_name
    FROM users u
    LEFT JOIN user_profiles p ON u.id = p.user_id
    WHERE u.id = #{userId}
</select>
 


接口文档的编写:

public interface UserMapper {
    User getUserWithOrder(Integer userId);
}

2.2一对多:

执行顺序:

  1. 首先,执行主表查询(getUserWithOrders)获取用户(User)的基本信息,包括用户的 id、username 和 email。

  2. 然后,根据主表查询结果中的用户 id,执行从表查询(getOrdersByUserId)来获取该用户的所有订单(Order)的信息,包括订单的 id 和 order_number。

  3. MyBatis 将根据查询结果自动将主表和从表的数据进行关联,构建出包含用户和其所有订单的复合对象Collection作用(User 类中包含一个 List<Order>):使用 <collection> 标签定义了一对多嵌套查询,它表示主表中的 orders 属性与从表 OrderItem 实体类的 List 进行关联,MyBatis 会根据 <resultMap> 中的映射规则自动将查询结果映射到 OrderItem 实体类,并将其添加到 Order 实体类的orders属性中。。

类实体的定义:

public class User {
    private Integer id;
    private String username;
    private String email;
    private List<Order> orders; // 一对多关系中,User 类包含一个 Order 类的 List

    // 省略构造函数、getters 和 setters 方法
}
 

public class OrderItem {
    private Integer id;
    private String ordernumber;
    // 其他 OrderItem 类的属性

    // 省略构造函数、getters 和 setters 方法
}
 

resultmap的编写:

<resultMap id="UserResultMap" type="User">
    <id property="id" column="user_id" />
    <result property="username" column="username" />
    <result property="email" column="email" />
    <collection property="orders" resultMap="OrderResultMap" /> <!-- 一对多关系中使用 <collection> 标签 -->
</resultMap>

<resultMap id="OrderResultMap" type="Order">
    <id property="id" column="order_id" />
    <result property="orderNumber" column="order_number" />
    <!-- 其他 Order 类的属性映射 -->
</resultMap>

多表查询语句的编写:

<select id="getUserWithOrders" resultMap="UserResultMap">
    SELECT u.id, u.username, u.email,
                   o.id, o.order_number
    FROM users u
    LEFT JOIN orders o ON u.id = o.id
    WHERE u.id = #{userId}
</select>

接口文档的编写:

public interface UserMapper {
    User getUserWithOrders(Integer userId);
}
 

2.3多对多:同理跟一对多,难度在于SQL语句编写

类的定义:

public class Order {
    private Integer id;
    private String orderNumber;
    private List<Item> items; // 多对多关系中,Order 类包含一个 Item 类的 List

    // 省略构造函数、getters 和 setters 方法
}

resultmap编写:

<resultMap id="OrderResultMap" type="Order">
    <id property="id" column="order_id" />
    <result property="orderNumber" column="order_number" />
    <collection property="items" resultMap="ItemResultMap" /> <!-- 多对多关系中使用 <collection> 标签 -->
</resultMap>

<resultMap id="ItemResultMap" type="Item">
    <id property="id" column="item_id" />
    <result property="itemName" column="item_name" />
    <!-- 其他 Item 类的属性映射 -->
</resultMap>

多表查询语句sql:

<select id="getOrderWithItems" resultMap="OrderResultMap">
    SELECT o.id AS order_id, o.order_number,
           i.id AS item_id, i.item_name
    FROM orders o
    INNER JOIN order_item oi ON o.id = oi.order_id
    INNER JOIN items i ON oi.item_id = i.id
    WHERE o.id = #{orderId}
</select>


接口文档的编写:

public interface OrderMapper {
    Order getOrderWithItems(Integer orderId);
}
 

3.Mybatis嵌套查询:将多表查询拆成多个单表查询再嵌套在一起

3.1多对一/一对一

类的定义

Order表中有User类型的user对象作为属性代表一对一

在一对一嵌套查询中,<association> 标签用于定义关联对象的映射规则,它包含了几个重要的参数,具体作用如下:

  1. property:表示主表实体类中关联对象的属性名,用于指定关联对象要映射到主表实体类的哪个属性上。例如,如果 property="user",那么表示关联对象 User 要映射到主表实体类 Order 的 user 属性上。

  2. javaType:表示关联对象的 Java 类型,用于指定关联对象的类型。例如,javaType="User" 表示关联对象是 User类型。

  3. column:表示从表中关联的字段名,用于指定从表中与主表关联的字段。例如,如果 column="u_id",那么表示从表中的 u_id 字段与主表关联。

  4. select:表示执行关联对象的查询语句(嵌套查询),用于获取关联对象的数据。它可以引用另一个映射语句的 ID,也可以直接在此处编写查询语句。通常情况下,我们使用该参数执行从表的查询,并将查询结果映射到关联对象。

3.2一对多

类的定义:

public class User {
    private Integer id;
    private String username;
    private String email;
    private List<Order> orders; // 一对多关系中,User 类包含一个 Order 类的 List

    // 省略构造函数、getters 和 setters 方法
}

public class Order {
    private Integer id;
    private String orderNumber;
    // 其他 Order 类的属性

    // 省略构造函数、getters 和 setters 方法
}
 

resultmap编写:

执行顺序:先执行getUserWithOrders得到对应的字段:u.id,u.username,u.email,u.o.id对应的值,并将其进行封装,在封装的过程中调用Collection标签对user中的order进行封装,利用Collection中的column参数将o.id的值传入,并利用select参数找到该方法执行奖结果封装到OrderResultMap,如此Order类中所有属性均被封装。

<resultMap id="UserResultMap" type="User">
    <id property="id" column="user_id" />
    <result property="username" column="username" />
    <result property="email" column="email" />
    <!-- 使用 <collection> 标签定义嵌套查询 -->
    <collection property="orders" ofType="Order" select="getOrdersByUserId" />
</resultMap>

<select id="getUserWithOrders" resultMap="UserResultMap">
    SELECT u.id AS user_id, u.username, u.email
    FROM users u
    WHERE u.id = #{userId}
</select>

<select id="getOrdersByUserId" parameterType="int" resultMap="OrderResultMap"(与userresultmap类似)>
    SELECT id AS order_id, order_number
    FROM orders
    WHERE user_id = #{userId}
</select>

接口文档的编写:

public interface UserMapper {
    User getUserWithOrders(Integer userId);
}

3.3多对多

 

三、加载策略及注册开发

1.Mybatis加载策略

延迟加载:在嵌套查询中,在需要用到数据才进行加载,不需要用到数据就不加载

原则:一对一采用立即加载,一对多多对多等用延迟加载

1.1实现策略

全局延迟加载:

设置出发延迟加载的方法:因为就算开启延迟加载,在不设置延迟加载方法前提下调用以下方法时也会触发立即加载

 

 

association和Collection标签中有fetchType=“lazy”即可开启

2.Mybatis缓存

2.1一级缓存:sqlsession级别的缓存,默认开启,当查询一个订单后再次查询即可发现不走数据库而是从Mybatis一级缓存中获取

分析:执行查询操作先从缓存中获取,执行增删改需要commit事务,会清除sqlsession中的缓存(除此之外 sqlsession.clearCache()和sqlsession.close和映射文件中select标签中flushCache设置为true也会清除缓存)

2.2二级缓存:namespace级别,跨sqlsession级别的缓存默认不开启,开启条件要求pojo可序列化并且在映射文件Mapper标签下<cache></cache>即可开启

每当执行完一级缓存后会刷新到二级缓存由于是Mapper映射级别缓存多个sqlsession共用一个二级缓存,读取存入缓存,增删改会清空缓存。

缺点:造成脏读:由于为namespace级别的在多表查询是比如user查询时会查询order信息这是user的namespace级别的,但在order的namespace中执行增删改不会影响user的二级缓存,造成脏读。

建议使用redis第三方缓存

3.Mybatis注解开发:注解开发不用再编写xml映射文件,核心配置文件扫描的是接口文件,实现了更简单的开发方式

3.1语法:

3.2实现步骤

创建接口文件:

编写核心配置文件:

 

 

 3.3复杂映射开发:取代resultmap,association,Collection标签

 

一对一嵌套查询:

Order接口文件:

当封装该方法的返回结果时,到最后一个result时,由于查询的表的结果中有uid这个列,利用uid这个结果通过column属性传入one中作为参数进行查询,将返回的结果通过javatype确定为user类封装到Order类中的user属性中

 

User接口文件:

接口文件名.方法名组成唯一标识在select属性中作为扫描标识

 

测试: 

 

 

一对多:可在@many中设置fetchtype是否懒加载

当封装该方法的返回结果时,到最后一个result时,由于查询的表的结果中有id这个列,利用id这个结果通过column属性传入many中作为参数进行查询,将返回的结果通过javatype确定为List类封装到User类中的orderList属性中

 

多对多:同理

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值