internet
和abatis
的组合,是一个基于Java的持久层框架。<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
目前使用的是mysql5.7.28
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
CREATE TABLE `student` (
`obj_id` varchar(70) NOT NULL COMMENT '主键',
`name` varchar(255) DEFAULT NULL COMMENT '姓名',
`age` int(11) DEFAULT NULL COMMENT '年龄',
PRIMARY KEY (`obj_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
注意:一般放在entity
包下,体现该类为数据表实体
一般命名要求:表名和实体类类名相同,实体类首字母大写;表字段名和属性名相同
public class Student {
private String obj_id;
private String name;
private String age;
// getter、setter
}
注意:一般放在dao
包下,体现该接口为数据表持久层接口
public interface StudentDao {
/**
* 查询全部学生
* @return
*/
List<Student> selectAll();
}
注意:一般放在mapper
包中,体现是持久层接口的映射文件;如果是Maven项目,一般放在resources/mapper
包中。文件名一般与持久层接口名保持一致,例如:StudentDao.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">
<!--namespace用于区别不同映射文件的SQL语句,必须是唯一的,一般是对应持久层接口的全限定名-->
<mapper namespace="top.ygang.mybatisdemo.dao.StudentDao">
<!-- id作为SQL语句的唯一标识,对应接口中的方法名 -->
<!-- resultType:返回对象的类全名 -->
<select id="selectAll" resultType="top.ygang.mybatisdemo.entity.Student">
select * from student
</select>
</mapper>
命名固定为:mybatis-config.xml
。并且放在src
目录;如果是Maven项目,放在resources
目录
<?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="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!-- 配置环境,可以包含多个环境,default属性值指定使用哪个环境节点配置 -->
<environments default="dev">
<!-- 环境节点的配置 -->
<environment id="dev">
<!-- 事务管理器 -->
<transactionManager type="JDBC"/>
<!-- 数据源,type:设置数据源的类型,pooled为使用默认连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 管理sql映射文件 -->
<mappers>
<!-- 持久层接口映射文件路径 -->
<mapper resource="mapper/StudentDao.xml"/>
</mappers>
</configuration>
public class Start {
public static void main(String[] args) throws Exception{
// 读取核心配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 由mybatis读核心配置文件,将读到的的信息缓存在SqlSessionFactory工厂对象中
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 从工厂中获取SqlSession对象,该对象可以进行数据库交互
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获取持久层接口实现类对象
StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
// 调用方法,执行sql
List<Student> students = studentDao.selectAll();
System.out.println(students);
// 关闭资源,释放数据库链接
sqlSession.close();
}
}
在少量简单SQL的情况下,我们并不需要特别声明一个持久层接口的映射文件,我们可以使用mybatis提供的注解@Select
,@Update
,@Delete
,@Insert
直接在持久层接口上面进行绑定sql。
例如上面的例子,我们的StudentDao
接口中只有一个sql,而且简单,我们就不需要写StudentDao.xml
,而是将StudentDao
修改下
public interface StudentDao {
/**
* 查询全部学生
* @return
*/
@Select("select * from student")
List<Student> selectAll();
}
由于我们没有了StudentDao.xml
,所以核心配置文件也需要改一下
<mappers>
<!-- 持久层接口映射文件路径 -->
<!-- <mapper resource="mapper/StudentDao.xml"/> -->
<!-- 持久层接口全限定名 -->
<mapper class="top.ygang.mybatisdemo.dao.StudentDao"/>
</mappers>
在MyBatis中,我们可以使用#{}
或${}
来获取接口形参,二者的区别在于
#{}
:是预编译,底层调用的是PreparedStatement
,会在预处理之前把参数部分用一个占位符?
替换,可以防止SQL注入,适用于值的操作${}
:底层调用的是Statement
,只是简单的字符串替换,SQL语句拼接,不能防止SQL注入,对于字符串的部分需要加单引号,如${a}
,如果拼接的是表名,列名,关键字,那么使用此方式最合适接口只有一个参数的情况下,我们直接可以使用#{fieldName}
获取
//接口
GoodType findById(int id);
<!--xml映射文件-->
<select id="findById">
select * from goodtype where id=#{id}
</select>
方式一:通过在xml映射文件标签中参数以arg0-argn
按照形参的顺序
//接口
boolean update1(String name,int id);
<!--xml映射文件-->
<update id="update1">
update goodbyte set tname=#{arg0} where id=#{arg1}
</update>
方式二:通过在xml映射文件标签中参数以param1-paramn
按照形参的顺序
//接口
boolean update2(String name,int id);
<!--xml映射文件-->
<update id="update2">
update goodbyte set tname=#{param1} where id=#{param2}
</update>
方式三:采用在接口形参前添加注解@Param
的方式**(常用)**
//接口
boolean update3(@Param("na")String name,@Param("i")int id);
<!--xml映射文件-->
<update id="update3">
update goodbyte set tname=#{n} where id=#{i}
</update>
方式四:如果多个参数是同一个自定义对象的属性,使用对象
//接口
boolean update4(GoodType goodType);
<!--xml映射文件,#{xx}:xx为属性名-->
<update id="update4">
update goodbyte set tname=#{tname} where id=#{tid}
</update>
方式五:使用Map集合的方式。缺点是,在编写sql语句的时候,无法确定map集合的key
//接口
boolean update5(Map<String,Object> map);
<!--xml映射文件,#{xx}:xx为map集合中对应的key-->
<update id="update5">
update goodbyte set tname=#{key1} where id=#{key2}
</update>
这个顺序是mybatis-config.xml
中,configuration
的子标签声明顺序,固定不可变
<?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>
<!--
The content of element type "configuration" must match
"(properties?,settings?,typeAliases?,typeHandlers?,
objectFactory?,objectWrapperFactory?,
reflectorFactory?,plugins?,environments?,
databaseIdProvider?,mappers?)".
-->
<!--引入properties文件,此后就可以在当前文件中使用${}的方式访问value-->
<properties resource="jdbc.properties"></properties>
<!--日志实现类,STDOUT_LOGGING为默认标准输出日志-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!--
typeAliases:设置类型别名,即为某个具体的类型(全类名)设置一个别名
在MyBatis的范围中,就可以使用别名表示一个具体的类型
-->
<typeAliases>
<!--
typeAlias标签属性:
type:设置需要起别名的标签
alias:设置某个类型的别名
(有默认的别名,默认别名就是类名(不是全类名噢),且不区分大小写)
-->
<!--<typeAlias type="top.ygang.entity.Student" alias="Student"></typeAlias>-->
<!--通过包设置类型别名,指定包下所有的类型将全部拥有默认的别名,即类名且不区分大小写-->
<!--
<package name="top.ygang.entity"/>
-->
</typeAliases>
<!--
environments:配置连接数据库的环境
属性:
default:设置默认使用环境的id
-->
<environments default="development">
<!--
environment:设置一个具体的连接数据库的环境
属性:
id:设置环境的唯一标识,不能重复
-->
<environment id="development">
<!--
transactionManager:设置事务管理器
属性:
type:设置事务的方式
type="JDBC|MANAGED"
JDBC:表示使用JDBC中原生的事务管理方式
MANAGED:被管理,例如Spring,mybatis的事务都交给Spring来管理
-->
<transactionManager type="JDBC"/>
<!--
dataSource:设置数据源
属性:
type:设置数据源的类型
type:"POOLED|UNPOOLED|JNDI"
POOLED:表示使用数据库连接池
UNPOOLED:不使用数据库连接池
JNDI:使用上下文中的数据源
-->
<dataSource type="POOLED">
<!--<property name="driver" value="com.mysql.jdbc.Driver"/>-->
<!--<property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=UTC"/>-->
<!--<property name="username" value="root"/>-->
<!--<property name="password" value="123456"/>-->
<!--配置信息通过${}读取jdbc.properties中的配置信息进行替换-->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!--引入映射文件-->
<mappers>
<!--<mapper resource="mappers/UserMapper.xml"/>-->
<!--
以包的方式引入映射文件,但是必须满足两个条件:
1、mapper接口和映射文件所在的包必须保持一致
2、mapper接口的名字必须和映射文件的名字保持一致
-->
<!--
<package name="com.atguigu.mybatis.mapper"/>
-->
</mappers>
</configuration>
1、引入依赖
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2、配置日志实现
<setting name="logImpl" value="LOG4J"/>
3、CLASSPATH
下添加log4j.properties
无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
例如现在表里的某个字段是int
类型,我们需要将他转换成Boolean
类型。当大于等于0时为true
;小于0时为false
,我们就需要自定义类型转换器
1、继承BaseTypeHandler
,并且使用注解@MappedJdbcTypes
指定该转换器处理的数据库字段类型;使用@MappedTypes
指明该转换器处理的Java类型
// DB字段类型
@MappedJdbcTypes(JdbcType.INTEGER)
// Java类型
@MappedTypes(Boolean.class)
public class MyTypeHandler extends BaseTypeHandler<Boolean> {
/**
* 增删改调用,从Java类型转DB类型
*/
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int columnIndex, Boolean aBoolean, JdbcType jdbcType) throws SQLException {
preparedStatement.setObject(columnIndex,aBoolean ? 1 : -1);
}
/**
* 查询调用,从DB类型转Java类型
*/
@Override
public Boolean getNullableResult(ResultSet resultSet, String columnName) throws SQLException {
int val = resultSet.getInt(columnName);
return val >= 0;
}
/**
* 查询调用,从DB类型转Java类型
*/
@Override
public Boolean getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException {
int val = resultSet.getInt(columnIndex);
return val >= 0;
}
/**
* 查询调用,从DB类型转Java类型
*/
@Override
public Boolean getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException {
int val = callableStatement.getInt(columnIndex);
return val >= 0;
}
}
这种配置方法将会在整个项目中生效
<typeHandlers>
<typeHandler handler="top.ygang.mybatisdemo.MyTypeHandler"/>
</typeHandlers>
对于查询,可以在resultMap
的result
标签中配置
<resultMap id="result_map" type="top.ygang.mybatisdemo.entity.Student">
<result typeHandler="top.ygang.mybatisdemo.MyTypeHandler" column="age" property="age"/>
</resultMap>
对于增删改
可以在insert
、delete
、update
标签中配置
update student set name = 'tom' where obj_id = #{id,typeHandler=top.ygang.mybatisdemo.MyTypeHandler}
MyBatis根据对关联对象查询的select语句的执行时机,分为三种类型:直接加载(默认)、侵入式延迟加载、深度延迟加载。
就是按需加载,我们需要什么的时候再去进行什么操作。而且先从单表查询,需要时再从关联表去关联查询,能大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
1、必须是分布查询
2、关联关系使用association(对一)或collection(对多)
执行主加载对象的查询时,不会执行对关联对象的查询,只有在访问主加载对象详情时或关联对象详情时,才会执行关联对象的查询
<!--在setting节点中加入-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="true"/>
执行主加载对象的查询时,不会执行对关联对象的查询,访问主加载对象详情时也不会执行对关联对象的查询,只有在访问关联对象的详情时,才会执行关联对象的查询
<!--在setting节点中加入-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
(可选)在<association>
或<collection>
中添加**fetchType = "lazy"**属性开启延迟,但这里默认为开启,可以不加
引入缓存之后,第一次去查询数据库,第二次以后,如果数据库的数据没有发生改变,我们就不去查询数据库,我们查缓存。使用缓存之后,就减少了查询数据库的次数,从而提高了查询效率
mybatis中的缓存可以分为一级缓存和二级缓存
1)不频繁更新的数据可以放在缓存中,频繁发生更新的数据,不要放在缓存中
2)财务数据不要放在缓存中,不要的数据可以放在缓存中
一级缓存mybatis自带,不要额外配置,属于sqlSession级别的缓存(和SqlSession相关的),默认开启
Goods find1 = goodsDaoMapper.findByGid(1);
System.out.println(find1.getDname());
System.out.println("**********************************");
Goods find2 = goodsDaoMapper.findByGid(1);
System.out.println(find2.getDname());
结果日志
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Opening JDBC Connection
Created connection 798244209.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@2f943d71]
==> Preparing: select * from goods where gid=?
==> Parameters: 1(Integer)
<== Columns: gid, dname, buyprice, saleprice, remarks, tid, picPath, quantity, state
<== Row: 1, java, 50, 80, 11111, 1, img/1.jpg, 1000, 1
<== Total: 1
java
**********************************
java
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@2f943d71]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@2f943d71]
Returned connection 798244209 to pool.
两次相同的查询,只向数据库发送了一次SQL语句,说明,第二次查询是从缓存中查询,并不是从数据库查询
第一次查询的数据和第二次查询的数据不一样
如果查询的是同样的数据,调用了更新方法(增删改DML)的方法
调用了刷新方法
sqlSession.clearCache();
SqlSession对象不一样
(select标签中)flushCache="true"
属性
如果系统需要二级缓存,那么需要人为配置,二级别缓存属于Mapper级别、SqlSessionFactory级别的缓存
将查询出的数据保存在二级缓存中需要通过调用sqlSession的commit方法才可以进行保存在二级缓存
主要用来解决一级缓存不能跨会话(SqlSession)共享的问题,范围是namespace 级别的,可以被多个SqlSession 共享(只要是同一个接口里面的相同方法,都可以共享),生命周期和应用同步
如果你的MyBatis使用了二级缓存,并且你的Mapper和select语句也配置使用了二级缓存,那么在执行select查询的时候,MyBatis会先从二级缓存中取输入,其次才是一级缓存,即MyBatis查询数据的顺序是:二级缓存 —> 一级缓存 —> 数据库
。
1、在核心配置文件的<setting>
节点中添加
<setting name="cacheEnabled" value="true"></setting>
2、在xml映射文件中,配置
<!--
可选属性:
type="哪种缓存机制"
size="缓存的资源数量"
eviction="缓存回收策略"
flushInterval="刷新的间隔"
readOnly="只读true\false"
true:会给所有的查询的用户,返回同一个的java对象
false:会给每一个查询的用户,返回同一个java对象的克隆的副本,也就是不同的对象
-->
<cache></cache>
3、封装类实现序列化接口java.io.Serializable
1、二级缓存是将对象通过序列化流存储到磁盘文件,需要封装类实现序列化,如果是多表联合查询,那么关联表对应的实体类也需要序列化
2、两个SqlSession对象必须是同一个SqlSessionFactory创建
SqlSession sqlSession1 = factory.openSession();
SqlSession sqlSession2 = factory.openSession();
GoodsDaoMapper mapper1 = sqlSession1.getMapper(GoodsDaoMapper.class);
GoodsDaoMapper mapper2 = sqlSession2.getMapper(GoodsDaoMapper.class);
Goods byGid1 = mapper1.findByGid(1);
System.out.println(byGid1.getDname());
sqlSession1.commit();
System.out.println("***********************************");
Goods byGid2 = mapper2.findByGid(1);
System.out.println(byGid2.getDname());
sqlSession1.close();
sqlSession2.close();
结果日志
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Cache Hit Ratio [dao.GoodsDaoMapper]: 0.0
Opening JDBC Connection
Created connection 1297149880.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4d50efb8]
==> Preparing: select * from goods where gid=?
==> Parameters: 1(Integer)
<== Columns: gid, dname, buyprice, saleprice, remarks, tid, picPath, quantity, state
<== Row: 1, java, 50, 80, 11111, 1, img/1.jpg, 1000, 1
<== Total: 1
java
***********************************
Cache Hit Ratio [dao.GoodsDaoMapper]: 0.5
java
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4d50efb8]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4d50efb8]
Returned connection 1297149880 to pool.
同一SqlSessionFactory对象,所产生的不同的SqlSession对象,也可以共享缓存
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据查询。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
1、使用Redis替换mybatis缓存,即使查询出来的数据为null,也会存入缓存
2、使用布隆过滤器,对一些不存在的条件进行过滤
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
1、设置热点数据缓存永不过期
2、利用代理或装饰模式,对mybatis源码进行切入,对数据库查询进行加锁
3、使用mybatis的拦截器,对源码中的query方法进行加锁
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是, 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
1、缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2、如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。
3、设置热点数据永远不过期