一、配置文件基本应用
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一
执行顺序:
-
首先,执行主表查询(
getUserWithProfile
)获取用户(User)的基本信息,包括用户的 id、username 和 email。 -
然后,根据主表查询结果中的用户 id,执行从表查询(
getUserProfileByUserId
)来获取该用户的用户资料(UserProfile)的信息,包括资料的 id 和 fullName。 -
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一对多:
执行顺序:
-
首先,执行主表查询(
getUserWithOrders
)获取用户(User)的基本信息,包括用户的 id、username 和 email。 -
然后,根据主表查询结果中的用户 id,执行从表查询(
getOrdersByUserId
)来获取该用户的所有订单(Order)的信息,包括订单的 id 和 order_number。 -
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>
标签用于定义关联对象的映射规则,它包含了几个重要的参数,具体作用如下:
-
property
:表示主表实体类中关联对象的属性名,用于指定关联对象要映射到主表实体类的哪个属性上。例如,如果property="user"
,那么表示关联对象User
要映射到主表实体类 Order 的 user 属性上。 -
javaType
:表示关联对象的 Java 类型,用于指定关联对象的类型。例如,javaType="User"
表示关联对象是 User类型。 -
column
:表示从表中关联的字段名,用于指定从表中与主表关联的字段。例如,如果column="u_id"
,那么表示从表中的u_id
字段与主表关联。 -
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属性中
多对多:同理