框架
设计模式:解决某一类问题的最优方案,没有具体的代码实现,只是一种技术或者一种思想
开发模式: MVC 为了优化代码的一种编程思想
工具类:代码的封装
框架: 很大的工具类 半成品 可以基于框架继续开发
使用框架的优点: 提高开发效率
使用框架的缺点: 执行效率会降低 可能会有隐藏的Bug
三层架构
web层
service层
持久层
使用mybatis只需要关注sql本身和结果集的封装
Mybatis的使用
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>groupId</groupId>
<artifactId>artifactId</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
创建核心配置文件
<?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">
<!-- mybatis的主配置文件 -->
<configuration>
<!-- 配置环境 -->
<environments default="dev">
<!-- 配置mysql的环境-->
<environment id="dev">
<!-- 配置事务的类型-->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源(连接池) -->
<dataSource type="POOLED">
<!-- 配置连接数据库的4个基本信息 -->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 -->
<mappers>
<mapper resource="dao.resource"/>
</mappers>
</configuration>
使用Mybatis
public static void main(String[] args)throws Exception {
//1.读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.使用工厂生产SqlSession对象
SqlSession session = factory.openSession();
//4.使用SqlSession创建Dao接口的代理对象
Dao dao = session.getMapper(dao.class);
//5.使用代理对象执行方法
List<User> users = userDao.findAll();
for(User user : users){
System.out.println(user);
}
//6.释放资源
session.close();
in.close();
}
字段大小写问题
MySQL数据库表中的字段在win系统中不区分大小写,但在linux中严格区分大小非
当JavaBean和数据库表中字段名不一致时
在写sql语句的时候可以给表中的字段起别名,使得别名和JavaBean中的属性名称一致
<!-- 配置 查询结果的列名和实体类的属性名的对应关系 -->
<resultMap id="userMap" type="com.itheima.domain.User">
<!-- 主键字段的对应 -->
<id property="userId" column="id"></id>
<!--非主键字段的对应-->
<result property="userName" column="username"></result>
<result property="userAddress" column="address"></result>
<result property="userSex" column="sex"></result>
<result property="userBirthday" column="birthday"></result>
</resultMap>
使用
resultMap="userMap"
连接池
池:使用空间换取执行事件的思想
连接池:实现了javax.sql.DataSource接口的实现类
常用的连接池:druid c3p0 dbcp
mybatis中的连接池
type的取值: POOLED 使用mybatis中自带连接池 会创建多个连接 关闭的时候会归还连接
UNPOOLED 使用mybatis中自带的连接池 使用的时候创建一个连接 使用完毕关闭连接
JNDI
如果使用c3p0就要自己写一个类继承UnpooledDataSourceFactory
这个类
type中写这个类的全路径 包名+类名
事务
概念
一个业务里涉及到多条sql语句操作,一定要使用事务控制,目的是保证当前业务的完整性,也就是这个业务中的多条sql语句必须同时成功或者同时失败,成功就commit失败就rollback
事务的隔离级别
事务的隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
uncommitted read | 可能 | 可能 | 可能 |
committed read | 不可能 | 可能 | 可能 |
repeatable read | 不可能 | 不可能 | 可能 |
serializable | 不可能 | 不可能 | 不可能 |
MySQL的默认隔离级别是 repeatable read
ORACLE的默认隔离级别是 committed read
事务四大特性
- 原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的概念,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。 - 一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。 - 隔离性(Isolation)
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。 - 持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
mybatis的事务控制
如果配置了<transactionManager type="JDBC"/>
默认就开启事务
会把Mysql的自动提交改为手动提交 执行完操作后必须要手动提交或者回滚 完成事务的持久性操作
如果想再改回自动提交
sqlSession = factory.openSession(true);
一般使用手动提交事务, 因为如果使用的是自动提交事务, 那么将不能进行事务的管理
多条件查询
<!-- 了解的内容:抽取重复的sql语句-->
<sql id="defaultUser">
select * from user
</sql>
<select id="findUserInIds" resultMap="userMap" parameterType="queryvo">
<!--
include标签可以提取一个sql在其他的标签中进行引用
如果是跨xml文件引用要加namespace.defaultUser
-->
<include refid="defaultUser"></include>
<!--where标签可以代替第一个where条件 where 1 = 1-->
<where>
<!--ids 是传递过来参数的属性名称-->
<if test="ids != null and ids.size()>0">
<!--
collection 要遍历的集合
open是开始拼接sql语句
close是拼接sql语句结束
遍历的结果会拼接到open和close之间
select * from user where 1 = 1 and id in (20,30)
separator是在拼接过程中的连接符
-->
<foreach collection="ids" open="and id in (" close=")" item="uid" separator=",">
#{uid}
</foreach>
</if>
</where>
</select>
多表关联查询
表与表之间的关系
一对一:
建表原则:
1.主键对应
2.在任何一方添加外键字段指向一的一方主键
一对多:
建表原则:在多的一方添加外键字段指向一的一方主键
多对多:
建表原则:建立一张中间表 这个中间表至少包括两个字段 分别指向两个表的主键
外键
- 物理外键: 有关键字来约束
foreign key
优点: 可以保证数据的完整性
缺点: 较影响性能 - 逻辑外键: 没有关键字约束 只是程序员思想层面上的约束 一般都是使用后台代码进行业务层面的约束
优点: 不会影响性能(因为数据库中没有添加外键)
缺点:需要人为的保证数据的完整性
如果是大型系统 数据的安全性要求较高 对速度不要求 建议使用物理外键 但如果要求较高的性能 数据安全不要求 建议使用逻辑外键
如果是小项目 随意
一对一的关联查询
这个一对一其实是多对一的特殊情况
在多对一中 多的一方的每个单独的对象针对一的一方来说 都可以看成是一对一的
Javabean设置: 在多的一方要有一的一方的对象引用属性
<!-- 定义封装account和user的resultMap
mybatis中的一对一数据封装
一的一方: User
多的一方: Account
JavaBean的写法
User里面有 List<Account>
Account里面有 User uu ;
查询Account表并且把相关的User查询出来
association :封装一的一方的对象
property:是Account实体类中的属性名称
column: 数据库中的表字段名 外键
javaType: 一的一方的数据类型 User实体类
-->
<resultMap id="accountUserMap" type="account">
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!-- 一对一的关系映射:配置封装user的内容-->
<association property="user" column="uid" javaType="user">
<id property="id" column="id"></id>
<result column="username" property="username"></result>
<result column="address" property="address"></result>
<result column="sex" property="sex"></result>
<result column="birthday" property="birthday"></result>
</association>
</resultMap>
一对多关联查询
Javabean的设置: 在一的一方要有一个List集合来保存多的一方的对象
<!--
定义User的resultMap
一对多的数据封装
先在一的一方的JavaBean中添加List<Account>
collection : 配置JavaBean中的list集合
property: list集合的属性名称
oftype : List集合中装的对象类型
如果SQL语句中使用了别名 这边要做对应的修改
-->
<resultMap id="userAccountMap" type="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
<!-- 配置user对象中accounts集合的映射 -->
<collection property="accounts" ofType="account">
<id column="aid" property="id"></id>
<result column="uid" property="uid"></result>
<result column="money" property="money"></result>
</collection>
</resultMap>
多对多关联查询
多对多可以看成是两个一对多
JavaBean的设置:在两个实体类中分别都使用List集合来保存对方的对象
多对多会设计到第三张表的存在 所以要注意SQL语句的写法
结果集的封装同一对多一样
延迟加载
即需要用到数据时才进行加载,不需要用到数据时就不加载数据,也成为懒加载
好处:先进行单表查询,需要时再去关联表关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
坏处:只有用到数据库的数据时,才会进行数据查询,这也在大批量处理数据的时候可能造成查询数据过长,使得用户体验下降
立即加载
只要一调用方法,马上发起查询
在对应的四种表关系中
一对多和多对多通常采用延迟加载
<collection property="accounts" ofType="account" column="id" select="com.itheima.dao.IAccountDao.findAccountByUid" ></collection>
多对一和一对一通常采用立即加载
<association property="user" column="uid" javaType="user" select="com.itheima.dao.IUserDao.findById"></association>
核心配置文件:
<settings>
<!--开启Mybatis支持延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"></setting>
</settings>
缓存机制
mybatis缓存分为两种:
一级缓存:即SqlSession级别的缓存,对于相同的查询,能够从缓存中返回结果而非查询数据库
二级缓存:二级缓存即Mapper级别的缓存,定义在Mapper文件的标签中并需要开启此缓存,多个Mapper文件可以共用一个缓存,依赖<cache-ref>
标签配置
一级缓存(默认开启)
一级缓存是SqlSession级别的,与有没有配置无关,只要SqlSession存在一级缓存就存在
调用session.close();
session.clearCache();
或执行增删改的方法的时候都会清空一级缓存
二级缓存(手动开启)
二级缓存基于mapper文件中的namespace,也就是多个sqlSession可以共享一个mapper中的二级缓存区域,并且如果两个mapper的namespace相同,那么这两个mapper执行sql查询的数据也会存在相同的二级缓存区域中
核心配置文件中
<!--开启二级缓存 -->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
映射配置文件中
<!-- 开启二级缓存 -->
<cache></cache>
mybatis中还可以配置
userCache
和flushCache
等配置项,userCache
是用来设置是否禁用二级缓存的,在statement中设置useCache=false
可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。
<select id="selectUserByUserId" useCache="false" resultType="com.itheima.domain.User" parameterType="int">
select * from user where id=#{id}
</select>
这种情况是针对每次查询都需要最新的数据sql,要设置成
useCache=false
,禁用二级缓存,直接从数据库中获取。
一般下执行完commit
操作(增删改)都需要刷新缓存,flushCache=true
表示刷新缓存,这样可以避免数据库脏读
增删改默认就是true 所以不用设置,默认即可
如果查询也需要保证每次都是最新数据 不从缓存中获取 则可以设置flushCache="true"
<select id="selectUserByUserId" flushCache="true" useCache="false" resultType="com.itheima.domain.User" parameterType="int">
select * from user where id=#{id}
</select>
如果定义了MyBatis二级缓存,那么MyBatis二级缓存读取优先级高于MyBatis一级缓存
注解方式
单表CRUD
public interface IUserDao {
/**
* 查询所有用户
* @return
*/
@Select("select * from user")
List<User> findAll();
/**
* 保存用户
* @param user
*/
@Insert("insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday})")
void saveUser(User user);
/**
* 更新用户
* @param user
*/
@Update("update user set username=#{username},sex=#{sex},birthday=#{birthday},address=#{address} where id=#{id}")
void updateUser(User user);
/**
* 删除用户
* @param userId
*/
@Delete("delete from user where id=#{id} ")
void deleteUser(Integer userId);
/**
* 根据id查询用户
* @param userId
* @return
*/
@Select("select * from user where id=#{id} ")
User findById(Integer userId);
/**
* 根据用户名称模糊查询
* @param username
* @return
*/
@Select("select * from user where username like #{username} ")
List<User> findUserByName(String username);
/**
* 查询总用户数量
* @return
*/
@Select("select count(*) from user ")
int findTotalUser();
}
一对多 多对多
public interface IAccountDao {
/**
* 查询所有账户,并且获取每个账户所属的用户信息
* 根据账户 查用户 多的一方 查一的一方 多对一 一对一
* Accout
* User user
*
* User
* List<Accout>
* @return
*/
@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.itheima.dao.IUserDao.findById",fetchType= FetchType.EAGER))
})
List<Account> findAll();
/**
* 根据用户id查询账户信息
* @param userId
* @return
*/
@Select("select * from account where uid = #{userId}")
List<Account> findAccountByUid(Integer userId);
}
多对一(一对一)
@CacheNamespace(blocking = true)
public interface IUserDao {
/**
* 查询所有用户
* @return
*/
@Select("select * from user")
@Results(id="userMap",value={
@Result(id=true,column = "id",property = "userId"),
@Result(column = "username",property = "userName"),
@Result(column = "address",property = "userAddress"),
@Result(column = "sex",property = "userSex"),
@Result(column = "birthday",property = "userBirthday"),
@Result(property = "accounts",column = "id",
many = @Many(select = "com.itheima.dao.IAccountDao.findAccountByUid",
fetchType = FetchType.LAZY))
})
List<User> findAll();
/**
* 根据id查询用户
* @param userId
* @return
*/
@Select("select * from user where id=#{id} ")
@ResultMap("userMap")
User findById(Integer userId);
/**
* 根据用户名称模糊查询
* @param username
* @return
*/
@Select("select * from user where username like #{username} ")
@ResultMap("userMap")
List<User> findUserByName(String username);
}