mybatis的官方文档网站 https://mybatis.org/mybatis-3/zh/index.html
目录
什么是框架
框架是某种应用的半成品,就是一组组件,是一套解决方案,供你选用完成自己的系统。框架帮你封装了一些细节,开发者可以使用极简的方式实现功能,可以提高程序的开发效率。
三层架构和框架
持久层技术解决方案
- JDBC技术:
Connection
PreparedStatement
ResultSet - Spring的JdbcTemplate:
Spring中对jdbc的简单封装 - Apache的DBUtils:
也是对jdbc的简单封装
以上这些都不是框架
jdbc是规范
JdbcTemplate和DBUtils都是工具类
MyBatis框架概述
mybatis是一个优秀的基于java的持久层框架,用java编写,它内部发封装了jdbc,使开发者只需要关注sql语句本身,而不需要话费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。
采用ORM思想解决了实体和数据库的映射,实现了结果集的封装
- ORM(对象关系映射)
就是把数据库和实体类的属性进行对应起来
让我们可以操作实体类实现操作数据库表
mybatis的环境搭建
1、创建maven工程导入坐标
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
2、创建实体类和dao的接口
3、创建mybatis的主配置文件
4、创建映射配置文件
环境搭建的注意事项
-
mybatis的映射文件位置必须和dao接口的包结构相同
-
在idea中创建目录时,它和包是不一样的
包在创建时,com.hwh是二级目录
目录在创建时,com.hwh 是一级目录 -
映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名
-
映射配置文件的操作配置(如select),id属性必须是dao接口的方法名
入门案例代码:(基于xml配置)
- 实体类
package com.hwh.entity;
import java.io.Serializable;
import java.util.Date;
/**
* @Description TODO
* @Author hwh
* @Date 2019/12/24 21:02
**/
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
}
- IUserDao
package com.hwh.dao;
import com.hwh.entity.User;
import java.util.List;
/**
* @Description TODO
* @Author hwh
* @Date 2019/12/24 21:06
**/
public interface IUserDao {
public List<User> findAllUser();
}
- sqlMapConfig.xml(mybatis的主配置文件)
<?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>
<!-- 配置环境-->
<environments default="mysql">
<environment id="mysql">
<!-- 配置事务的类型-->
<transactionManager type="JDBC"/>
<!-- 配置数据源(连接池)-->
<dataSource type="POOLED">
<!-- 配置连接数据库的4个基础信息-->
<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=""/>
</dataSource>
</environment>
</environments>
<!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件-->
<mappers>
<mapper resource="com/hwh/dao/IUserMapper.xml"/>
</mappers>
</configuration>
- IUserMapper.xml(mapper映射文件)
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hwh.dao.IUserDao">
<!--id需与接口中的方法名称一致-->
<select id="findAllUser" resultType="com.hwh.entity.User">
select * from user
</select>
</mapper>
- test.java(测试类)
import com.hwh.dao.UserDao;
import com.hwh.entity.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class test {
private InputStream in;
private SqlSession sqlSession;
private UserDao userDao;
@Before
public void init() throws IOException {
in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
sqlSession = factory.openSession();
userDao = sqlSession.getMapper(UserDao.class);
}
@After
public void destroy() throws IOException {
in.close();
sqlSession.close();
}
@Test
public void testAllUsers(){
List<User> users = userDao.findAllUsers();
for(User user:users){
System.out.println(user);
}
}
}
- 基于注解配置
public interface IUserDao {
@Select("select * from user")
public List<User> findAllUser();
}
<mapper class="com.hwh.dao.IUserDao"></mapper>
获取插入数据的id
<!--保存用户-->
<insert id="saveUser" parameterType="com.hwh.entity.User">
<!--获取插入数据行的id-->
<!--keyProperty为属性名 keyColumn为列名-->
<selectKey keyProperty="id" keyColumn="id" resultType="java.lang.Integer" order="AFTER">
select last_insert_id();
</selectKey>
insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
</insert>
模糊查询(两种方式)
/**
* 根据名字进行模糊查询
*/
@Test
public void findUserByName(){
// List<User> users = userDao.findUserByName("%a%");
List<User> users = userDao.findUserByName("a");
for(User user:users){
System.out.println(user);
}
}
<!--根据名字模糊查询-->
<select id="findUserByName" parameterType="java.lang.String" resultType="com.hwh.entity.User">
<!--select * from user where username like #{name}-->
select * from user where username like '%${value}%'
</select>
- 注意
#表示为PreparedsStatement的占位符,可以防止sql注入,一般用这种方式
$为Statement的字符串拼接。
log4j
#设置日志的级别 ,多个以,分开(没有给出的,则不会被输出)
log4j.rootLogger=debug,A,R
#
log4j.appender.logfile.encoding=utf-8
#DailyRollingFileAppender每天产生一个日志文件
log4j.appender.R=org.apache.log4j.DailyRollingFileAppender
#设置日志文件保存路径
log4j.appender.R.File=logs/log.log //这里的是你输出到日志文件的路径
#日志输出格式
log4j.appender.R.layout.ConversionPattern=%-d{yyyy-MM-dd HH\:mm\:ss} [%c]-[%p] %m%n
#设置日志文件后缀名,决定着多长时间创建一个新的文件!yyyyMMdd每天一个,yyyyMMddHH第小时一个,...
log4j.appender.R.DatePattern='.'yyyy-MM-dd
#日志布局格式
log4j.appender.R.layout=org.apache.log4j.PatternLayout
#输出到控制台
log4j.appender.A=org.apache.log4j.ConsoleAppender
log4j.appender.A.layout=org.apache.log4j.PatternLayout
log4j.appender.A.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%c]-[%p] %m%n
mybatis的参数深入
parameterType(输入类型)
- 传递简单类型
- 传递pojo对象(也就是javabean对象)
mybatis使用ognl表达式解析对象的值,#{}或者${}括号中的值为pojo属性名称。
扩展:OGNL表达式(Apache开发)
全称 Object graphic navigation language(对象图导航语言)
它是通过对象的取值方法来获取数据,在写法上把get给省略了
如:我们获取用户的名称
类中的写法:user.getUsername();
OGNL表达式写法:user.username
mybatis 中为什么直接写username而不用写user.,是因为在parameterType中已经提供了属性所属的类,所以此时不需要写对象名。 - 传递pojo包装对象
当表字段与类属性名不一致时的两种解决方案
- 查询时使用别名
示例代码:
select id as userId,username as userName from user
- 使用resultMap使字段名与类属性名对应(推荐使用,可以提高开发效率)
示例代码
<resultMap id="userMap" type="com.hwh.entity.User">
<!--主键字段的对应-->
<id property="userId" column="id"></id>
<!--非主键字段的对应-->
<result property="userAddress" column="address"></result>
</resultMap>
注意:在window系统下mysql不区分大小写,但是在linux系统下时严格区分大小写的
Properties标签
- 配置properties
可以再标签内部配置连接数据库的信息,也可以通过属性引用外部配置文件信息
resources属性(常用)
用于指定配置文件的位置,是按照类路径的写法来写,并且必须存在于类路径下
url属性:
是要求按照Url的写法来写地址
URL Uniform Resource Locator 统一资源定位符,它是可以唯一标识资源的位置
它的写法:
http://localhost:8080/mybatis/demoServlet
协议 主机 端口 URI
URI: Uniform Resource Identifier 统一资源标识符,它是在应用中可以唯一定位一个资源的。
<properties resource="application.properties">
<!-- <property name="driver" value="com.mysql.jdbc.Driver"/>-->
<!-- <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=UTF8"/>-->
<!-- <property name="username" value="root"/>-->
<!-- <property name="password" value=""/>-->
</properties>
typeAliases
作用: 用于配置别名,它只能配置类的别名
连接池
我们在实际开发中都会实用连接池,因为它可以减少我们获取连接所消耗的时间。
连接池就是用于存储连接的一个容器。
容器其实是一个集合对象,该集合必须是线程安全的,不能两个线程拿到同一个连接。
该集合必须实现队列的特性:先进先出
mybatis中的连接池
mybatis连接池提供来3中方式的配置:
-
配置的位置
主配置文件SqlMapConfig.xml(主配置文件)的dataSource标签,type属性就是表示采用何种连接池方式。
type属性的取值:
POOLED 采用传统的javax.sql.DateSource规范中的连接池,mybatis中有针对规范的实现
UPOOLED 采用传统的获取连接的方式,虽然也实现来javax.sql.DataSource接口,但是并没有实用池的思想(每次用都重新获取一个连接)
JNDI 采用服务器提供JNDI技术实现来获取dataSource对象,不同的服务器所能拿到的dataSource是不一样的
注意:如果不是web或者maven的war工程,是不能实用的
一般使用的tomcat服务器,采用的连接池就是dbcp连接池。 -
POOLED原理
当需要获取一个连接时,首先看空闲池中是否有连接,若有则返回一个连接,若没有则看活动池中是否已经达到最大数量,若没达到,则建立一个连接,若已达到最大数量,则将最老的连接拿过来用。
事务(待写)
- 什么是事务
事务由单独单元的一个或者多个sql语句组成,在这个单元中,每个mysql语句时相互依赖的。而整个单独单元作为一个不可分割的整体,如果单元中某条sql语句一旦执行失败或者产生错误,整个单元将会回滚,也就是所有受到影响的数据将会返回到事务开始以前的状态;如果单元中的所有sql语句均执行成功,则事务被顺利执行。 - 事务的四大特性(ACID)
1)原子性
原子性指事务不可分割,要么全部执行,要么全部不执行
2)一致性
事务必须使数据库在事务执行前与执行后保持一个一致的状态(如银行转账,总金额仍一致)
3)隔离性
多个并发事务之间相互隔离,互补干扰
4)持久性
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。 - 不考虑隔离性会产生的3个问题
1)脏读
一个事务读取了另一个事物未提交的数据
2)不可重复读
一个事务多次读取表中数据,结果不一致
不可重复读和脏读的区别是,脏读是读取前一事务未提交的脏数据,不可重复读是重新读取了前一事务已提交的数据。
3)幻读(虚读)
一个事务读取了别的事务插入的数据导致前后读取结果不一致 - 解决办法:四种隔离级别(隔离级别由低到高)
1)Read uncommitted(读未提交)
一个事务可以读取另一个未提交事务的数据。
2)Read committed(度提交)
就是一个事务要等另一个事务提交后才能读取数据。
3)Repeatable read(重复读)
就是在开始读取数据(事务开启)时,不再允许修改操作
4)Serializable(序列化)
是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。
注意:大多数数据库默认的事务隔离级别是Read committed,比如Sql Server , Oracle。Mysql的默认隔离级别是Repeatable read。
mybatis中的事务
sqlSession的commit和rollback方法实现事物的提交和回滚
sqlSession = factory.openSession(ture);
通过设置为true可以将其设置为自动提交
mybatis中的动态sql语句
- if判断语句
<select id="findByCondition" parameterType="user" resultType="user">
select * from user where 1=1
<if test="username!=null">
and username=#{username}
</if>
</select>
- where标签
加上where标签可以省略上面的where 1=1,效果等同于上面的代码
<select id="findByCondition" parameterType="user" resultType="user">
select * from user
<where>
<if test="username!=null">
and username=#{username}
</if>
</where>
</select>
- foreach标签实现in查询
<select id="findInId" parameterType="queryvo" resultType="user">
select * from user
<where>
<foreach collection="ids" open="and id in (" close=")" item="uid" separator=",">
#{uid}
</foreach>
</where>
</select>
抽取重复的sql语句
<sql id="defaultSql">
select * from user
</sql>
<select id="findAllUsers" resultType="user">
<include refid="defaultSql"></include>
<!--select * from user-->
</select>
mybatis中的查询
表之间的关系
- 一对多
- 多对一
- 一对一
- 多对多
mybatis中一对一的多表查询
主要代码
- accountMapper.xml(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">
<mapper namespace="com.hwh.dao.AccountDao">
<resultMap id="accountMap" type="account">
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<association property="user" column="uid" javaType="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="birthday" column="birthday"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
</association>
</resultMap>
<select id="findAllAccount" resultMap="accountMap">
select u.*,a.id as aid,uid,money from account a,user u where a.uid=u.id
</select>
</mapper>
- Account类(将user封装进account类)
package com.hwh.entity;
public class Account {
private Integer id;
private Integer uid;
private Float money;
private User user;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public Float getMoney() {
return money;
}
public void setMoney(Float money) {
this.money = money;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", uid=" + uid +
", money=" + money +
", user=" + user +
'}';
}
}
mybatis中一对多的查询
<resultMap id="userMap" type="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="birthday" column="birthday"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<collection property="account" ofType="account">
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
</collection>
</resultMap>
<select id="findAllUsers" resultMap="userMap">
SELECT
`u`.*, `a`.`id` AS 'aid', `uid`, `money`
FROM
`user` u
LEFT JOIN `account` a ON `a`.`uid` = `u`.`id`
</select>
mybatis中的延迟加载
问题:在一对多中,当我们有一个用户,他有100个账户
在查询用户的时候,要不要把关联的账户查出来?
在查询账户的时候,要不要把关联的用户查出来?
在查询用户时,用户下的账户应该是,什么时候使用,什么时候查询
在查询账户时,账户的所属用户信息应该是随着账户查询时一起查询出来
-
什么是延迟加载
在真正使用数据时次发起查询,不使用的时候不查询,按需加载(懒加载) -
什么是立即加载
不管用不用,只要一调用方法,马上发起查询 -
在对应的四种表关系中:
一对多,多对多:通常情况下我们都是采用延迟加载
多对一,一对一:通常情况下我们都是采用立即加载
一对一的延迟加载
- 配置信息,开启延迟加载
<!--配置信息-->
<settings>
<!--延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,每种属性将会按需加载。-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
- mapper文件
<resultMap id="accountMapDelay" type="account">
<id property="id" column="id"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!--一对一的关系迎合:配置封装user的内容
select属性指定的内容:查询用户的唯一标识
column属性指定的内容:用户根据id查询时,所需要的参数的值
-->
<association property="user" column="uid" javaType="user" select="com.hwh.dao.UserDao.findUserById">
</association>
</resultMap>
<select id="findAll" resultMap="accountMapDelay">
select * from account
</select>
一对多的延迟加载
同样需要加上配置信息,如上一对多
- 主要代码(mapper配置文件)
<resultMap id="userMapDelay" type="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="birthday" column="birthday"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<collection property="accounts" column="id" ofType="account" select="com.hwh.dao.AccountDao.findAccountById">
</collection>
</resultMap>
mybatis中的缓存
- 什么是缓存
存在于内存中的临时数据 - 为什么使用缓存
减少和数据库的交互次数,提高执行效率 - 什么样的数据能使用缓存,什么样的数据不能使用
使用于缓存:
1)经常查询,且不经常改动
2)数据的正确与否对最终结果影响不大的
不适用于缓存:
1)经常改变的数据
2)数据的正确与否对最终结果影响很大的
例如:商品的库存,银行的汇率,股市的牌价
mybatis中的一级缓存和二级缓存
一级缓存
它指的是mybatis中SqlSession对象的缓存
当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中
该区域的结构是一个Map,当我们再次查询相同的数据时,mybatis会先去SqlSession中查询是否存在,若存在则直接返回
当SqlSession对象消失时,mybatis的一级缓存也就消失了
- 清空缓存
一级缓存SqlSession范围的缓存,当调用SqlSession的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。
通过clerCache()方法也可以清空缓存。
二级缓存
它指的是mybatis中SqlSessionFactory对象的缓存,由同一个SqlSessionFactory对象创建SqlSession共享其缓存。
注意:二级缓存中存储的是数据而不是对象,当需要拿取数据时,会新建一个对象将数据塞入。
- 二级缓存的使用步骤
1)让mybatis框架支持二级缓存(在主配置文件中配置setting)
<setting name="cacheEnabled" value="true"/>
2)让当前的映射文件支持二级缓存(在mampe中配置)
<cache/>
3)让当前的操作支持二级缓存(在select标签中配置)
userCache="true"
mybaits基于注解的配置
主配置文件
<?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>
<properties resource="sqlConfig.yml"></properties>
<typeAliases>
<!--类名就是别名,不区分大小写-->
<package name="com.hwh.entity"/>
</typeAliases>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="com.hwh.dao"/>
</mappers>
</configuration>
CRUD操作
/**
* 查询所有用户
*/
@Select("select * from user")
@Results(id = "userMap",value = {
@Result(id = true,property = "id",column = "id"),
@Result(property = "userName",column = "username"),
@Result(property = "birthday",column = "birthday"),
@Result(property = "sex",column = "sex"),
@Result(property = "address",column = "address")
})
public List<User> findAllUser();
/**
* 通过id查询用户
* @return
*/
@Select("select * from user where id=#{id}")
@ResultMap("userMap")
public User findUserById(Integer id);
/**
* 根据名字模糊查询
* @param username
* @return
*/
@Select("select * from user where username like #{username}")
public List<User> findUserByName(String username);
/**
* 更新用户信息
* @param user
*/
@Update("update user set username=#{userName},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}")
public void updateUser(User user);
/**
* 保存数据
* @param user
*/
@Insert("insert into user(username,birthday,sex,address) values(#{userName},#{birthday},#{sex},#{address})")
public void saveUser(User user);
/**
* 删除数据
* @param id
*/
@Delete("delete from user where id = #{id}")
public void deleteUser(Integer id);
使用延迟加载,一对一关系
@Select("select * from account")
@Results(id="accountMap",value = {
@Result(id = true,column = "id",property = "id"),
@Result(column = "uid",property = "uid"),
@Result(column = "money",property = "money"),
@Result(property = "user",column = "uid",one = @One(select = "com.hwh.dao.IUserDao.findUserById",fetchType = FetchType.LAZY))
})
public List<Account> findAllAccount();
使用延迟加载,一对多关系
@Select("select * from user")
@Results(id = "userMapDelay",value = {
@Result(id = true,property = "id",column = "id"),
@Result(property = "userName",column = "username"),
@Result(property = "birthday",column = "birthday"),
@Result(property = "sex",column = "sex"),
@Result(property = "address",column = "address"),
@Result(property = "accounts",column = "id",
many = @Many(select = "com.hwh.dao.IAccountDao.findAccountById",
fetchType = FetchType.LAZY))
})
public List<User> findAll();
基于注解开启二级缓存
1)让mybatis框架支持二级缓存(在主配置文件中配置setting)
<setting name="cacheEnabled" value="true"/>
2)在dao类头部添加注解
@CacheNamespace(blocking = true)
来源:CSDN
作者:佛系小菜鸡
链接:https://blog.csdn.net/qq_40224261/article/details/103684640