文章目录
一.传统的jdbc开发存在的问题
-
jdbc使用数据库用的时候就连接,使用完毕后需要关闭,这样频繁的开关数据库会影响数据库的性能
- 解决方案:使用数据库连接池
-
将sql语句编码到java代码中,有一天需要改变sql,需要重新编译java代码 ,不利于系统维护
- 解决方案: 可以将sql配置到一个单独的xml文件中
-
PreparedStatement对象对sql语句中的参数?进行预编译处理 ,也存在硬编码到java代码中,不利于系统维护
- 解决方案: 可以将sql和参数配置到一个单独的xml文件中
-
ResultSet遍历数据的时候,存在硬编码 ,不利于系统维护
- 解决方案: 将查询到的结果集直接映射成java对象
二.Mybatis框架
Mybatis是一款优秀的持久层框架,用于简化JDBC开发,它本是apache的一个开源项目iBatis框架,类似于简历模板,只要提供了持久化类与表的映射关系,ORM框架在运行时就能参照映射文件的信息,把对象持久化到数据库中,当前ORM框架主要有五种:Hibernate(Nhibernate),iBatis,mybatis,EclipseLink,JFinal
mybatis中文网:https://mybatis.net.cn/
三.开发步骤
1.SqlMapConfig.xml配置文件
2.创建实体类
3.添加映射文件如User.xml
- namespace为命名空间
- parameterType为输入类型参数(形参的类型)
- resultType为输出类型参数(返回值的类型)
注:
1.无论是输入参数还是输出参数,除了基本数据类型之外全部要用全限定名
2.返回值类型是集合时也需要写实体类的全限定名
<mapper namespace="test">
<select id="selectUserById" parameterType="java.lang.Integer" resultType="com.sunyanshu.entity.User">
select *
from User
where id = #{id}
</select>
</mapper>
4.在 SqlMapConfig.xml中配置User.xml文件
<mappers>
<mapper resource="com/liushao/po/User.xml"/>
</mappers>
5.定义配置文件
String resource = "SqlMapConfig.xml";
6.构建流读取配置文件
InputStream is = Resources.getResourceAsStream(resource);
7.创建会话工厂
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(is);
8.获取会话
SqlSession sqlSession = ssf.openSession();
9.调用SqlSession对象的相关方法执行操作
第一个参数为命名空间.statementid,第二个参数为实参
User user = sqlSession.selectOne("test.selectUserById", 1);
**注:
1.增删改需要提交事务
10.关闭会话
sqlSession.close();
四.经典面试题:#{}和${}的区别
- #{}和$ {}都是参数占位符
- #{}是预编译处理,${}是直接替换
- #{}在执行sql时会将#{}替换为?,运行时自动设置参数值,如果只有一个参数会自动对应(即大括号内写什么都行),#{}会把{}内的值整体看成value,最后再给value加上单引号,即对sql语句进行预编译处理,可以防止SQL注入
- MyBatis在处理$ {} 时,会把$ {}替换成变量的值,不会加引号处理,所以容易出现sql注入问题
五.原始dao开发模式
1.开发思路
程序员需要写dao接口(dao)和dao实现类(daoimpl) ,需要在daoimpl中注入SqlSessionFactory对象
六.Mapper代理模式
1.开发规范
1.在mapper.xml中namespace必须等于接口的全限定名
<mapper namespace="com.sunyanshu.mapper.UserMapper">
2.mapper.java接口中的方法名必须和 mapper.xml中的statementId保持一致
//接口中的方法名
public User selectUserById(Integer id);
//mapper.xml中的statementId
<select id="selectUserById" parameterType="java.lang.Integer" resultType="com.sunyanshu.entity.User">
3.mapper.java接口中的输入参数类型要和 mapper.xml中的parameterType指定的类型一致
//接口中方法的形参类型
public User selectUserById(Integer id);
//mapper.xml中的输入类型参数
<select id="selectUserById" parameterType="java.lang.Integer" resultType="com.sunyanshu.entity.User">
4.mapper.java接口中的输出参数类型要和 mapper.xml中的resultType指定的类型一致
//接口中方法的返回值类型
public User selectUserById(Integer id);
//mapper.xml中的输出类型参数
<select id="selectUserById" parameterType="java.lang.Integer" resultType="com.sunyanshu.entity.User">
注:即使接口中的方法需要返回集合,xml文件中的输出类型参数也要写类的全限定名
七.resultMap
resultMap用来解决映射时列名和属性名不一致的问题
- type属性的值为返回值类型
- 主键在id标签中映射
- 其余列在result中映射
- column为数据库表中的列名
- property为实体类中的属性名
八.条件查询
1.多条件查询
xml配置文件中
<!--多条件查询-->
<select id="selectByCondition" parameterType="com.sunyanshu.entity.User" resultType="com.sunyanshu.entity.User">
select *
from user
where username like #{username}
and sex = #{sex}
</select>
测试类中
private SqlSession sqlSession = null;
@Before
public void setup() throws IOException {
//定义配置文件
String resource = "SqlMapConfig.xml";
//构建流读取配置文件
InputStream is = Resources.getResourceAsStream(resource);
//创建会话工厂
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(is);
//获取会话
sqlSession = ssf.openSession();
}
@Test
public void selectByCondition() {
//获取代理
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setUsername("%小%");
user.setSex("1");
//调用方法
List<User> users = userMapper.selectByCondition(user);
for (User user1 : users) {
System.out.println(user1);
}
//关闭资源
sqlSession.close();
}
运行结果如下
2.动态条件查询(动态sql)
2.1where标签、if标签
xml配置文件中
<!--动态条件查询-->
<select id="selectMoveByCondition" parameterType="com.sunyanshu.entity.User" resultType="com.sunyanshu.entity.User">
select *
from user
<where>
<if test="sex!=null and sex!=''">
and sex=#{sex}
</if>
<if test="username!=null and username !=''">
and username like #{username}
</if>
</where>
</select>
测试类中
//动态sql
@Test
public void selectMoveByCondition() {
//获取代理
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//构建实体类对象
User user = new User();
user.setSex("1");
// user.setUsername("%小%");
//调用方法
List<User> userList = userMapper.selectMoveByCondition(user);
for (User user1 : userList) {
System.out.println(user1);
}
}
2.2foreach标签
- collection:待遍历的集合
- item:遍历出来的对象
- open:开始遍历时拼接的串
- close:结束遍历时拼接的串
- separator:分隔符
xml配置文件中
<where>
<!--方式一-->
<foreach collection="idList" item="id" open="and (" close=")" separator="or">
id=#{id}
</foreach>
<!--方式二-->
<foreach collection="idList" item="id" open="and id in(" close=")" separator=",">
#{id}
</foreach>
</where>
User实体类中设置私有的id集合属性,并设置getset
测试类中
//动态sql
@Test
public void selectMoveByCondition() {
//获取代理
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//构建实体类对象
User user = new User();
// user.setSex("1");
// user.setUsername("%小%");
//构建集合
List<Integer> list = new ArrayList<>();
list.add(16);
list.add(22);
list.add(38);
list.add(1);
user.setIdList(list);
//调用方法
List<User> userList = userMapper.selectMoveByCondition(user);
for (User user1 : userList) {
System.out.println(user1);
}
}
九.sql片段
<select id="selectMoveByCondition" parameterType="com.sunyanshu.entity.User" resultType="com.sunyanshu.entity.User">
select *
from user
<where>
<include refid="selectMoveByConditionSql"/>
</where>
</select>
<!--sql片段-->
<sql id="selectMoveByConditionSql">
<if test="sex!=null and sex!=''">
and sex=#{sex}
</if>
<if test="username!=null and username !=''">
and username like #{username}
</if>
</sql>
十.多表查询
1.开发思路
- 先判断哪个是主表
- 判断多表之间的关系(如一个订单有多条订单明细(一对多),一个订单只属于一个用户(一对一),用户和商品(多对多))
- 判断使用什么映射关系(内连接?外连接?)
- 写sql语句
注:对象使用association标签进行映射,返回值为
2.resultMap映射原理
sql语句查询出来的结果在resultMap中进行映射,即把数据库中的字段映射到实体类对象中,因此,resultMap中没有映射的字段在对象中查询为空
3.一对一
- 需求:查询订单,关联查询创建订单的用户信息
- 实体类关系:一个订单只属于一个用户,在订单实体类中设置如下:
private User user;
,并设置getset
xml配置文件中
<select id="selectOrdersUser" resultMap="selectOrdersUserResultMap">
select orders.*,
user.sex,
user.birthday,
user.address
from orders
join user on orders.user_id = user.id
</select>
<resultMap id="selectOrdersUserResultMap" type="com.sunyanshu.entity.Orders">
<!--订单信息-->
<id column="id" property="id"></id>
<result column="user_id" property="userId"></result>
<result column="number" property="number"></result>
<!-- <result column="createtime" property="createtime"></result>-->
<result column="note" property="note"></result>
<!--用户信息-->
<association property="user" javaType="com.sunyanshu.entity.User">
<id column="id" property="id"></id>
<result column="sex" property="sex"></result>
<result column="birthday" property="birthday"></result>
<result column="address" property="address"></result>
</association>
</resultMap>
4.一对多
- 需求:查询订单以及订单明细
- 实体类关系:一个订单中有多条订单明细,在订单实体类中设置如下:
private List<Orderdetail> orderdetailList;
,并设置getset
xml配置文件中
<select id="selectOrdersOrderdetail" resultMap="selectOrdersOrderdetailResultMap">
select orders.*,
orderdetail.orders_id,
orderdetail.items_id,
orderdetail.items_num
from orders
join orderdetail on orders.id = orderdetail.orders_id
</select>
<resultMap id="selectOrdersOrderdetailResultMap" type="com.sunyanshu.entity.Orders">
<!--订单信息-->
<id column="id" property="id"></id>
<result column="user_id" property="userId"></result>
<result column="number" property="number"></result>
<result column="createtime" property="createtime"></result>
<result column="note" property="note"></result>
<!--订单明细信息-->
<collection property="orderdetailList" ofType="com.sunyanshu.entity.Orderdetail">
<result column="orders_id" property="ordersId"></result>
<result column="items_id" property="itemsId"></result>
<result column="items_num" property="itemsNmu"></result>
</collection>
</resultMap>
5.多对多
- 需求:查询用户及用户购买的商品
- 实体类关系:一个用户拥有多个订单,一个订单中有多条订单明细,一个订单明细中有一种商品
xml配置文件中
<select id="selectUserItems" resultMap="selectUserItemsResultMap">
select user.*,
orders.id ordersId,
orders.createtime,
orderdetail.id orderdetailId,
orderdetail.orders_id,
orderdetail.items_id,
orderdetail.items_num,
items.id itemsId,
items.name,
items.price,
items.detail,
items.createtime
from user,
orders,
orderdetail,
items
where user.id = orders.user_id
and orders.id = orderdetail.orders_id
and orderdetail.items_id = items.id
</select>
<resultMap id="selectUserItemsResultMap" type="com.sunyanshu.entity.User">
<!--用户信息-->
<id column="id" property="id"></id>
<result column="username" property="username"></result>
<result column="birthday" property="birthday"></result>
<result column="sex" property="sex"></result>
<result column="address" property="address"></result>
<!--订单信息-->
<collection property="ordersList" ofType="com.sunyanshu.entity.Orders">
<id column="ordersId" property="id"></id>
<!-- <result column="createtime" property="createtime"></result>-->
<!--订单明细信息-->
<collection property="orderdetailList" ofType="com.sunyanshu.entity.Orderdetail">
<id column="orderdetailId" property="id"></id>
<result column="orders_id" property="ordersId"></result>
<result column="items_id" property="itemsId"></result>
<result column="items_num" property="itemsNmu"></result>
<!--商品信息-->
<association property="items" javaType="com.sunyanshu.entity.Items">
<id column="itemsId" property="id"></id>
<result column="name" property="name"></result>
<result column="price" property="price"></result>
<result column="detail" property="detail"></result>
<result column="createtime" property="createtime"></result>
</association>
</collection>
</collection>
</resultMap>
十一.缓存
1.一级缓存
- 一级缓存是sqlSession级别的缓存
- 当执行sql语句时,首先会在sqlSession中查询,没查询到则会去数据库中查询并保存到缓存区,当再次执行同一sql语句时,会去缓存中查询,此时能查询到数据,就不会再去查询数据库了
- 提交事务会清空缓存区
//一级缓存
@Test
public void selectUserByIdCaChe1() {
//获取代理
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用方法
User user1 = userMapper.selectUserById(38);
//清空缓存
userMapper.deleteUserById(42);
sqlSession.commit();
User user2 = userMapper.selectUserById(38);
System.out.println(user1 == user2);
sqlSession.close();
}
2.二级缓存
- 二级缓存是mapper级别的缓存
- 想要被缓存,实体类必须实现序列化接口
- 二级缓存是跨sqlSession的
- 提交事务会清空缓存
- 步骤如下:
- 在mybatis配置文件中配置如下
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true" />
- 在UserMapper.xml中配置如下
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
测试类中
@Test
public void selectUserByIdCaChe2() throws IOException {
//获取会话
SqlSession sqlSession1 = ssf.openSession();
SqlSession sqlSession2 = ssf.openSession();
//获取代理
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
//调用方法
User user1 = userMapper1.selectUserById(38);
//删除数据
userMapper1.deleteUserById(27);
//提交事务
sqlSession1.commit();
sqlSession1.close();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = userMapper2.selectUserById(38);
System.out.println(user1 == user2);
sqlSession2.close();
}
十二.延迟加载(懒加载)
- 延迟加载(懒加载)即按照需要进行加载,只能使用resultMap来实现
- association、collection具备延迟记载功能
- 需求:查询订单关联查询用户信息,使用延迟加载会先查订单,当需要查询用户信息时再去查询用户信息,把对于用户信息的查询叫做按需查询
- 步骤:
- 导jar包
- 在mybatis配置文件中配置如下
<settings>
<!--打开延迟加载开关-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--将积极加载改为消极加载-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>