文章目录
1.就从JDBC开始吧
1.1. JDBC是啥
JDBC是java访问数据库的基石,JDO、Hibernate、MyBatis等只是更好的封装了JDBC。JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统(MySQL、O)、通用的SQL数据库存取和操作的公共接口(一组API)。
1.2.如何用JDBC连接数据库
spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/online_xdclass?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=1111
VS.
user=root//JDBC链接
password=1111
url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
driverClass=com.mysql.jdbc.Driver
数据库连接
@Test
public void testConnection5() throws Exception {
//1.加载配置文件
InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
pros.load(is);
//2.读取配置信息
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClass = pros.getProperty("driverClass");
//3.加载驱动
Class.forName(driverClass);
//4.获取连接
Connection conn = DriverManager.getConnection(url,user,password);
System.out.println(conn);
}
1.3.SQL 注入的前世今生
SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令
SELECT user, password FROM user_table WHERE user='a' OR 1 = ' AND password = ' OR '1' = '1'
从而利用系统的 SQL 引擎完成恶意行为的做法。
对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement(从Statement扩展而来) 取代 Statement 就可以了。PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句。
1.4.两种思想
- 面向接口编程的思想
- ORM思想(object relational mapping)
- 一个数据表对应一个java类
- 表中的一条记录对应java类的一个对象
- 表中的一个字段对应java类的一个属性
2. Mybatis
Mybatis是一个sql映射框架,提供的数据库的操作能力。增强的JDBC。减轻使用JDBC的复杂性,不用编写重复的创建 Connetion , Statement 。 不用编写关闭资源代码。直接使用java对象,表示结果数据。让开发者专注 SQL 的处理。
2.1初体验
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>
<!--下划线自动映射驼峰字段-->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/xdclass?serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="1111"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/VideoMapper.xml"/>
</mappers>
</configuration>
main
public static void main(String [] args) throws IOException {
String resouce = "config/mybatis-config.xml";
//读取配置文件
InputStream inputStream = Resources.getResourceAsStream(resouce);
//构建Session工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//获取Session
//这是jdk8语法(设置file > file structure > modules),可以自动关闭sqlSession
try(SqlSession sqlSession = sqlSessionFactory.openSession()){
VideoMapper videoMapper = sqlSession.getMapper(VideoMapper.class);
Video video = videoMapper.selectById(44);
//System.out.println(video.toString());
//通过注解
//List<Video> videoList = videoMapper.selectList();
List<Video> videoList = videoMapper.selectListByXML();
System.out.println(videoList.toString());
}
}
dao层的两种方式
public interface VideoMapper {
Video selectById(@Param("video_id") int videoId);//结合XML配置文件
List<Video> selectListByXML();
@Select("select * from video")//注解配置
List<Video> selectList();
}
resources下的mapper
<?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">
<!--
namespace: 名称空间,一般需要保持全局唯一, 最好是和dao层的java接口一致,
可以映射 sql语句 到对应的方法名称和参数、返回类型
mybatis是使用接口动态代理
-->
<mapper namespace="net.xdclass.online_class.dao.VideoMapper">
<!--
statement sql
id: 当前mapper下需要唯一
resultType : sql查询结果集的封装
-->
<select id="selectById" resultType="net.xdclass.online_class.domain.Video">
select * from video where id = #{video_id}
</select>
<select id="selectListByXML" resultType="net.xdclass.online_class.domain.Video">
select * from video
</select>
</mapper>
2.2.进阶-增删改查
2.2.1.多参数模糊查询
查询指定评分,标题包含指定字符的视频
dao层
List<Video> selectByPointAndTitleLike(@Param("point") double point, @Param("title") String title);
resources下的mapper
<select id="selectByPointAndTitleLike" resultType="net.xdclass.online_class.domain.Video">
select * from video where point=#{point} and title like concat('%', #{title},'%')
</select>
2.2.2.配置驼峰字段映射java对象和数据库字段
2.2.3.新增和自增主键
新增一条
<insert id="add" parameterType="net.xdclass.online_class.domain.Video" useGeneratedKeys="true" keyProperty="id" keyColumn="id" >
INSERT INTO `video` ( `title`, `summary`, `cover_img`, `price`, `create_time`, `point`)
VALUES
(#{title,jdbcType=VARCHAR},#{summary,jdbcType=VARCHAR},#{coverImg,jdbcType=VARCHAR},#{price,jdbcType=INTEGER},
#{createTime,jdbcType=TIMESTAMP},#{point,jdbcType=DOUBLE});
</insert>
在main函数中操作插入
Video video = new Video();
video.setTitle("小滴课堂面试专题900道");
video.setCoverImg("xdclass.net/aaa.png");
video.setPoint(9.4);
video.setCreateTime(new Date());
video.setPrice(9900);
video.setSummary("这个是面试专题概要");
int rows = videoMapper.add(video);
如何批量插入?利用foreach
<!--批量插入-->
<insert id="addBatch" parameterType="net.xdclass.online_class.domain.Video" useGeneratedKeys="true" keyProperty="id" keyColumn="id" >
INSERT INTO `video` ( `title`, `summary`, `cover_img`, `price`, `create_time`, `point`)
VALUES
<foreach collection="list" item="video" separator=",">
(#{video.title,jdbcType=VARCHAR},#{video.summary,jdbcType=VARCHAR},#{video.coverImg,jdbcType=VARCHAR},
#{video.price,jdbcType=INTEGER},
#{video.createTime,jdbcType=TIMESTAMP},#{video.point,jdbcType=DOUBLE})
</foreach>
</insert>
List<Video> list = new ArrayList<>();
list.add(video1);
list.add(video2);
int rows = videoMapper.addBatch(list);
2.2.3.更新
<update id="updateVideo" parameterType="net.xdclass.online_class.domain.Video">
update video
set
title = #{title,jdbcType=VARCHAR},
summary = #{summary,jdbcType=VARCHAR},
cover_img = #{coverImg,jdbcType=VARCHAR},
price = #{price,jdbcType=INTEGER},
create_time = #{createTime,jdbcType=TIMESTAMP},
point = #{point,jdbcType=DOUBLE}
where
id = #{id}
</update>
Video video = new Video();
video.setId(59);
video.setTitle("小滴课堂面试专题900道 2021年新版");
video.setCoverImg("xdclass.net/6666.png");
videoMapper.updateVideo(video);
选择性更新
利用Mybatis里面的 if test
语法
<update id="updateVideoSelective" parameterType="net.xdclass.online_class.domain.Video">
update video
<trim prefix="set" suffixOverrides=",">
<if test="title != null "> title = #{title,jdbcType=VARCHAR},</if>
<if test="summary != null "> summary = #{summary,jdbcType=VARCHAR},</if>
<if test="coverImg != null "> cover_img = #{coverImg,jdbcType=VARCHAR},</if>
<if test="price != 0 "> price = #{price,jdbcType=INTEGER},</if>
<if test="createTime !=null "> create_time = #{createTime,jdbcType=TIMESTAMP},</if>
<!--一定要看pojo类里面的是基本数据类型,还是包装数据类型-->
<if test="point != null "> point = #{point,jdbcType=DOUBLE},</if>
</trim>
where
id = #{id}
</update>
2.2.4.删除
<delete id="deleteByCreateTimeAndPrice" parameterType="java.util.Map">
delete from video where create_time <![CDATA[ > ]]> #{createTime} and price <![CDATA[ >= ]]> #{price}
</delete>
Map<String,Object> map = new HashMap<>();
map.put("createTime","2021-01-11 09:33:20");
map.put("price",9000);
int rows = videoMapper.deleteByCreateTimeAndPrice(map);
总结:
1.为了避免与标签的< >混淆,大于小于这样表示
大于等于 <![CDATA[ >= ]]>
小于等于 <![CDATA[ <= ]]>
2.parameterType指的是接收的数据类型(能够增删改得)
2.3.配置mybatis-config.xml
2.3.1.起个别名呗
<typeAliases>
<!--<typeAlias type="net.xdclass.online_class.domain.Video" alias="Video"/>-->
<package name="net.xdclass.online_class.domain"/>
<!--这个包下的类都以最后一级(即video)为别名,上下两种写法效果一样-->
</typeAliases>
配置修改后,resources > mapper下则可以简化
<!--<select id="selectById" parameterType="java.lang.Integer" resultType="net.xdclass.online_class.domain.Video">-->
<select id="selectById" parameterType="java.lang.Integer" resultType="Video">
select * from video where id = #{video_id,jdbcType=INTEGER}
</select>
2.3.2.sql片段
查询指定值,并不是用select * 把所有字段全部查出
<sql id="base_video_field">
id,title,summary,cover_img
</sql>
<select id="selectById" parameterType="java.lang.Integer" resultType="Video">
select <include refid="base_video_field"/> from video where id = #{video_id,jdbcType=INTEGER}
</select>
2.4.复杂查询(进阶)
2.4.1.resultMap
resultType只能查java对应的pojo类,在多表查询条件下,需要查指定字段则不能胜任。所以需要resultMap(等于一个自定义的字段集合,可能是几个表共有的)
<resultMap id="VideoResultMap" type="Video">
<!--
id 指定查询列的唯一标示
column 数据库字段的名称
property pojo类的名称
-->
<id column="id" property="id" jdbcType="INTEGER" />
<result column="video_tile" property="title" jdbcType="VARCHAR" />
<result column="summary" property="summary" jdbcType="VARCHAR" />
<result column="cover_img" property="coverImg" jdbcType="VARCHAR" />
</resultMap>
<select id="selectBaseFieldByIdWithResultMap" resultMap="VideoResultMap">
select id , title as video_tile, summary, cover_img from video where id = #{video_id}
</select>
2.4.2.association
用于查询复杂对象,比如查询视频订单,除了具有基本属性外,还有user对象属性。就在这时利用association返回指定resultMap
<resultMap id="VideoOrderResultMap" type="VideoOrder">
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="out_trade_no" property="outTradeNo"/>
<result column="create_time" property="createTime"/>
<result column="state" property="state"/>
<result column="total_fee" property="totalFee"/>
<result column="video_id" property="videoId"/>
<result column="video_title" property="videoTitle"/>
<result column="video_img" property="videoImg"/>
<!--
association 配置属性一对一
property 对应videoOrder里面的user属性名
javType 这个属性的类型
-->
<association property="user" javaType="User">
<id property="id" column="user_id"/>
<result property="name" column="name"/>
<result property="headImg" column="head_img"/>
<result property="createTime" column="create_time"/>
<result property="phone" column="phone"/>
</association>
</resultMap>
<!--一对一管理查询订单, 订单内部包含用户属性-->
<select id="queryVideoOrderList" resultMap="VideoOrderResultMap">
select
o.id id,
o.user_id ,
o.out_trade_no,
o.create_time,
o.state,
o.total_fee,
o.video_id,
o.video_title,
o.video_img,
u.name,
u.head_img,
u.create_time,
u.phone
from video_order o left join user u on o.user_id = u.id
</select>
2.4.3.association
collection: 一对多查询结果查询映射,比如user有多个订单
<resultMap id="UserOrderResultMap" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="headImg" column="head_img"/>
<result property="createTime" column="create_time"/>
<result property="phone" column="phone"/>
<!--
property 填写pojo类中集合类属性的名称
ofType 集合里面的pojo对象
-->
<collection property="videoOrderList" ofType="VideoOrder">
<!--配置主键,管理order的唯一标识-->
<id column="order_id" property="id"/>
<result column="user_id" property="userId"/>
<result column="out_trade_no" property="outTradeNo"/>
<result column="create_time" property="createTime"/>
<result column="state" property="state"/>
<result column="total_fee" property="totalFee"/>
<result column="video_id" property="videoId"/>
<result column="video_title" property="videoTitle"/>
<result column="video_img" property="videoImg"/>
</collection>
</resultMap>
<select id="queryUserOrder" resultMap="UserOrderResultMap">
select
u.id,
u.name,
u.head_img,
u.create_time,
u.phone,
o.id order_id,
o.out_trade_no,
o.user_id,
o.create_time,
o.state,
o.total_fee,
o.video_id,
o.video_title,
o.video_img
from user u left join video_order o on u.id = o.user_id
</select>
2.4.多级缓存和懒加载
2.4.1.Mybatis一级缓存
- 简介:一级缓存的作用域是SQLSession,同一个SqlSession中执行相同的SQL查询(相同的SQL和参数),第一次会去查询数据库并写在缓存中,第二次会直接从缓存中取(默认开启)。
- 当执行SQL时候两次查询中间发生了增删改的操作,即insert、update、delete等操作commit后会清空该SQLSession缓存; 比如sqlsession关闭,或者清空等
2.4.2.Mybatis二级缓存
- 简介:二级缓存是namespace级别的,多个SqlSession去操作同一个namespace下的Mapper的sql语句,多个SqlSession可以共用二级缓存,如果两个mapper的namespace相同,(即使是两个mapper,那么这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中,但是最后是每个Mapper单独的名称空间)
- 基于PerpetualCache 的 HashMap本地缓存(底层也是hash),可自定义存储源,如 Ehcache/Redis等, 默认是没有开启二级缓存
- 开启某个二级缓存步骤
mapper.xml里面配置
<!--开启mapper的namespace下的二级缓存-->
<!--
eviction:代表的是缓存回收策略,常见下面两种。
(1) LRU,最近最少使用的,一处最长时间不用的对象
(2) FIFO,先进先出,按对象进入缓存的顺序来移除他们
flushInterval:刷新间隔时间,单位为毫秒,这里配置的是100秒刷新,如果不配置它,当SQL被执行的时候才会去刷新缓存。
size:引用数目,代表缓存最多可以存储多少个对象,设置过大会导致内存溢出
readOnly:只读,缓存数据只能读取而不能修改,默认值是false
-->
<cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024"/>
mybatis-config.xml
全局配置:
<settings>
<!--这个配置使全局的映射器(二级缓存)启用或禁用缓存,全局总开关,这里关闭,mapper中开启了也没用-->
<setting name="cacheEnabled" value="true" />
</settings>
- 如果需要控制全局mapper里面某个方法不使用缓存,可以配置 useCache=“false”
<select id="selectById" parameterType="java.lang.Integer" resultType="Video" useCache="false">
select <include refid="base_video_field"/> from video where id = #{video_id,jdbcType=INTEGER}
</select>
- 一级缓存和二级缓存使用顺序
优先查询二级缓存 > 查询一级缓存 > 数据库
2.4.3.懒加载
按需加载,先从单表查询,需要时再从关联表去关联查询,能大大提高数据库性能,并不是所有场景下使用懒加载都能提高效率
- Mybatis懒加载: resultMap里面的association、collection有延迟加载功能
- 设置
<!--全局参数设置-->
<settings>
<!--延迟加载总开关-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--将aggressiveLazyLoading设置为false表示按需加载,默认为true-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
- 两个点
1、VideoOrderMapper.xml 用到关联查询
2.main
实验结果
2.5.事务
2.5.1. Mybatis的事务管理形式
快捷键:ctrl+shift+N
查找 TransactionFactory 接口
快捷键:Ctrl + Alt + B
查看接口的实现类
-
使用JDBC的事务管理
使用 java.sql.Connection对象完成对事务的提交(commit())、回滚(rollback())、关闭(close()) -
使用MANAGED的事务管理
MyBatis自身不会去实现事务管理,而让程序的容器如(Spring, JBOSS)来实现对事务的管理
2.5.2.两种存储引擎的区别
区别项 | Innodb | myisam |
---|---|---|
事务 | 支持 | 不支持 |
锁粒度 | 行锁,适合高并发 | 表锁,不适合高并发 |
是否默认 | 默认 | 非默认 |
支持外键 | 支持外键 | 不支持 |
适合场景 | 读写均衡,写大于读场景,需要事务 | 读多写少场景,不需要事务 |
全文索引 | 可以通过插件实现, 更多使用ElasticSearch | 支持全文索引 |
MyISAM不支持事务,如果需要事务则改为innodb引擎 更改数据库的表里面的引擎