框架
? 框架会将很多基本功能进行封装,程序员在框架基础上再进行业务开发
作用:简洁代码,高效开发
mybatis概述
? 原apache的一个开源项目,2010迁移到谷歌,更名为mybatis
特点
? 它是一款半自动的ORM持久层框架,具有较高的SQL灵活性,支持高级映射(一对一,一对多),动态SQL,延迟加载和缓存等特性,但它的数据库无关性较低
MVC
数据持久层(数据访问层 / M层):DAO,model
数据控制层(C层 control):servlet
前端(View)
ORM
Object Relation Mapping,对象关系映射。对象指的是Java对象,关系指的是数据库中的关系模型
? 对象关系映射,指的就是在Java对象和数据库的关系模型之间建立一种对应关系,类中的属性和表中的列一一对应。
半自动ORM框架优点
? 用mybatis进行开发,需要手动编写SQL语句。而全自动的ORM框架,如hibernate,则不需要编写SQL语句。用hibernate开发,只需要定义好ORM映射关系,就可以直接进行CRUD操作了。由于mybatis需要手写SQL语句,所以它有较高的灵活性,可以根据需要,自由地对SQL进行定制,也因为要手写SQL,当要切换数据库时,SQL语句可能就要重写,因为不同的数据库有不同的方言(Dialect),所以mybatis的数据库无关性低。虽然mybatis需要手写SQL,但相比JDBC,它提供了输入映射和输出映射,可以很方便地进行SQL参数设置,以及结果集封装。并且还提供了关联查询和动态SQL等功能,极大地提升了开发的效率。并且它的学习成本也比hibernate低很多
mybatis架构
mybatis搭建使用
导入MyBatis使用的jar包
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
创建 MyBatis 全局配置文件
? 在resources目录下创建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>
<properties resource="config.properties"/>
<settings>
<setting name="logImpl" value="LOG4J"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="lazyLoadTriggerMethods" value=""/>
</settings>
<typeAliases>
<typeAlias type="com.ff.mybatisPro.model.AdminUser" alias="AdminUser"/>
<typeAlias type="com.ff.mybatisPro.model.Dept" alias="Dept"/>
<typeAlias type="com.ff.mybatisPro.model.Employee" alias="Employee"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="UNPOOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${userName}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/AdminMapper.xml"/>
<mapper resource="mapper/DeptMapper.xml"/>
<mapper resource="mapper/EmployeeMapper.xml"/>
</mappers>
</configuration>
在mapper目录下创建对应的mapper接口并定义方法
package com.ff.mybatisPro.mapper;
import com.ff.mybatisPro.model.AdminUser;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
public interface AdminMapper {
void saveAdmin(AdminUser adminUser);
void updateAdmin(AdminUser adminUser);
void deleteAdmin(int id);
AdminUser selectAdminById(int id);
}
在resources.mapper目录下创建对应的mapper.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">
<mapper namespace="com.ff.mybatisPro.mapper.AdminMapper">
<insert id="saveAdmin" parameterType="AdminUser" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
insert into admin(account, password, sex)
values (#{account}, #{password}, #{sex})
</insert>
<update id="updateAdmin">
update admin
set account=#{account},
password=#{password},
sex=#{sex}
where id = #{id}
</update>
<delete id="deleteAdmin">
delete
from admin
where id = #{id}
</delete>
<select id="selectAdminById" resultType="AdminUser">
select *
from admin
where id = #{id}
</select>
</mapper>
在mybatis-config.xml核心配置文件中添加mapper映射
<mappers>
<mapper resource="mapper/AdminMapper.xml"/>
</mappers>
在util包下封装sqlSession创建过程
package com.ff.mybatisPro.util;
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 java.io.IOException;
import java.io.Reader;
public class MybatisUtil {
static SqlSessionFactory sessionFactory = null;
static {
Reader reader = null;
try {
reader = Resources.getResourceAsReader("mybatis-config.xml");
} catch (IOException e) {
e.printStackTrace();
}
sessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
public static SqlSession openSession(){
return sessionFactory.openSession();
}
}
在测试中调用方法
@Test
public void saveAdmin(){
AdminUser adminUser = new AdminUser();
adminUser.setAccount("admin2");
adminUser.setPassword("000");
adminUser.setSex("男");
SqlSession sqlSession = MybatisUtil.openSession();
AdminMapper mapper = sqlSession.getMapper(AdminMapper.class);
mapper.saveAdmin(adminUser);
System.out.println(adminUser.getId());
sqlSession.commit();
sqlSession.close();
}
@Test
public void updateAdmin(){
AdminUser adminUser = new AdminUser();
adminUser.setId(1);
adminUser.setAccount("admin2");
adminUser.setPassword("000");
adminUser.setSex("男");
SqlSession sqlSession = MybatisUtil.openSession();
AdminMapper mapper = sqlSession.getMapper(AdminMapper.class);
mapper.updateAdmin(adminUser);
sqlSession.commit();
sqlSession.close();
}
@Test
public void deleteAdmin(){
SqlSession sqlSession = MybatisUtil.openSession();
AdminMapper mapper = sqlSession.getMapper(AdminMapper.class);
mapper.deleteAdmin(1);
sqlSession.commit();
sqlSession.close();
}
@Test
public void selectAdmin(){
SqlSession sqlSession = MybatisUtil.openSession();
AdminMapper mapper = sqlSession.getMapper(AdminMapper.class);
AdminUser adminUser = mapper.selectAdminById(5);
System.out.println(adminUser);
sqlSession.commit();
sqlSession.close();
}
参数传递
? 传单个参数一般用parameterType,要指定参数就要使用@Param(‘acc’)形式
@Param(‘acc’)传参
AdminUser selectAdminByAccount(@Param("acc") String account, @Param("pwd") String password);
<select id="selectAdminByAccount" resultType="AdminUser">
select *
from admin
where account = #{acc}
and password = #{pwd}
</select>
@Test
public void selectAdminByAccount(){
SqlSession sqlSession = MybatisUtil.openSession();
AdminMapper mapper = sqlSession.getMapper(AdminMapper.class);
AdminUser adminUser = mapper.selectAdminByAccount("admin","000");
System.out.println(adminUser);
sqlSession.commit();
sqlSession.close();
}
Map键/值串传参
AdminUser getAdmin(Map<String, Object> map);
<select id="getAdmin" resultType="AdminUser" parameterType="hashmap">
select *
from admin
where account = #{acc}
and password = #{pwd}
</select>
@Test
public void getAdmin(){
SqlSession sqlSession = MybatisUtil.openSession();
AdminMapper mapper = sqlSession.getMapper(AdminMapper.class);
Map<String,Object> map = new HashMap<>();
map.put("acc","admin");
map.put("pwd","000");
AdminUser adminUser = mapper.getAdmin(map);
System.out.println(adminUser);
sqlSession.commit();
sqlSession.close();
}
结果返回
简单类型输出映射
int countAdmin();
<select id="countAdmin" resultType="java.lang.Integer">
select count(*)
from admin
</select>
POJO对象输出映射
? resultType=“AdminUser”:返回值的类型 根据返回值的类型,创建一个对象或List 将数据库数据自动映射到java对象中,表中的类名与类中的属性名相同
List<AdminUser> adminList();
<select id="adminList" resultType="AdminUser">
select *
from admin
</select>
关联查询resultMap
? 在关联查询的时候,一个对象中需要存放其他类的属性,我们通常直接把其他类添加到该类属性中,如下把部门关联的员工集合和操作人都添加为部门的属性
public class Dept {
private int id;
private String name;
private List<Employee> employees;
private AdminUser adminUser;
}
? 之后在查询语句中,resultMap中就可以去设置属性字段的映射
而封装的其他对象个体就会创建一个association标签配置该类的映射
对象集合会创建一个collection标签配置映射
<resultMap id="DeptMap" type="Dept">
<id column="id" property="id"/>
<result column="dname" property="name"/>
<association property="adminUser" javaType="AdminUser">
<result column="account" property="account"/>
</association>
<collection property="employees" javaType="list" ofType="Employee">
<result column="ename" property="name"/>
</collection>
</resultMap>
<select id="getDeptById" resultMap="DeptMap">
SELECT d.id,d.name dname,emp.name ename,a.account
FROM dept d LEFT JOIN employee emp ON d.id=emp.deptId
LEFT JOIN admin a ON d.adminId=a.id
WHERE d.id=#{id}
</select>
主键返回
? 在插入一条记录后,得到数据库自动生成的这条记录的主键id
<insert id="saveAdmin" parameterType="AdminUser" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
insert into admin(account, password, sex)
values (#{account}, #{password}, #{sex})
</insert>
添加log4j日志组件
导入jar坐标
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
在resources下导入log4j.properties文件
log4j.rootLogger = debug,stdout,D
#System out Console
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%p] %d{yyyy-MM-dd HH:mm:ss,SSS} %m%n
#System out File
log4j.appender.D = org.apache.log4j.FileAppender
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = F://java project/logs/log.log
log4j.appender.D.Append = true
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] -[%l] %m%n
在mybatis-config.xml中添加设置
<setting name="logImpl" value="LOG4J"/>
单元测试
导入jar包
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>provided</scope>
</dependency>
懒加载
? 对于关联查询,若不采用延迟加载策略,而是一次性将关联的从信息都查询出来,则在主信息比较多的情况下,会产生N+1问题,导致性能降低。比如在查询员工信息时,设置了关联查询部门信息,如不采用延迟加载策略,查询员工信息时就会把其部门信息全部查询出来,但我们却用不到,就会造成浪费。而懒加载就会让在需要部门信息的时候才执行查询部门的sql
@Test
public void getEmpById1(){
SqlSession sqlSession = MybatisUtil.openSession();
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
Employee employee = employeeMapper.getEmpById1(1);
System.out.println(employee.getName());
System.out.println(employee.getDept().getName());
System.out.println(employee.getAdminUser().getAccount());
sqlSession.commit();
sqlSession.close();
}
mybatis-config,xml中设置属性:
<setting name="lazyLoadTriggerMethods" value=""/>
Employee getEmpById1(int id);
<resultMap id="EmpMap1" type="Employee">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<association property="dept" javaType="Dept" fetchType="lazy"
select="getDeptById" column="deptId">
<result property="name" column="name"/>
</association>
<association property="adminUser" javaType="AdminUser" fetchType="lazy"
select="getAdminById" column="adminId">
<result property="account" column="account"/>
</association>
</resultMap>
<select id="getEmpById1" resultMap="EmpMap1">
select id,name,age,deptId,adminId from employee where id=#{id}
</select>
<select id="getAdminById" resultType="AdminUser">
select * from admin where id=#{id}
</select>
<select id="getDeptById" resultType="Dept">
select * from dept where id=#{id}
</select>
动态sql
where
当需要如上筛选查询时,返回的都是员工列,所以servlet中只用写一套方法,让在其sql中动态添加条件就行
- name和age在第一次加载页面时传向servlet的值为null,在空值查询时为 ’ ’
- where 可以根据里边的if是否有成立, 动态添加where关键字 会自动删除and,or等开头的关键字
public class EmployeeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter out = null;
resp.setContentType("text/html;charset=utf-8");
try {
out = resp.getWriter();
String name = req.getParameter("name");
String age = req.getParameter("age");
Employee employee = new Employee();
employee.setName(name);
if (age!=null && !age.equals("")){
employee.setAge(Integer.parseInt(age));
}
EmployeeService employeeService = new EmployeeService();
List<Employee> empList = employeeService.getEmpList(employee);
out.println(new Gson().toJson(empList));
} catch (Exception e) {
e.printStackTrace();
out.println(500);
}
}
}
public class EmployeeService {
public List<Employee> getEmpList(Employee employee){
SqlSession sqlSession = MybatisUtil.openSession();
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
List<Employee> employees = employeeMapper.getEmpList(employee);
sqlSession.commit();
sqlSession.close();
return employees;
}
}
public interface EmployeeMapper {
List<Employee> getEmpList(Employee employee);
}
<select id="getEmpList" parameterType="Employee" resultMap="EmpMap">
SELECT emp.id,emp.name ename,emp.age,d.name dname,a.account
FROM employee emp LEFT JOIN dept d ON emp.deptId=d.id
LEFT JOIN admin a ON emp.adminId=a.id
<where>
<if test="name!=null & name !='' ">
emp.name=#{name}
</if>
<if test="age!=null & age !='' ">
and emp.age=#{age}
</if>
</where>
</select>
trim
动态修改
? trim prefix=“where” prefixOverrides=“and||or”
- 当if有成立条件时,添加一个指定的前缀prefix (where)
- 如果以prefixOverrides开头,可以覆盖掉关键字(and,or)
<select id="getEmpList" parameterType="Employee" resultMap="EmpMap">
SELECT emp.id,emp.name ename,emp.age,d.name dname,a.account
FROM employee emp LEFT JOIN dept d ON emp.deptId=d.id
LEFT JOIN admin a ON emp.adminId=a.id
<trim prefix="where" prefixOverrides="and||or">
<if test="name!=null & name !='' ">
emp.name=#{name}
</if>
<if test="age!=null & age !='' ">
and emp.age=#{age}
</if>
</trim>
</select>
动态删除
? trim prefix=“set” suffixOverrides="," suffix=“where”
- 当有if成立时,添加前缀set,后缀where
- 如果以","结尾,直接删除
<update id="updateEmp" parameterType="Employee">
update employee
<trim prefix="set" suffixOverrides="," suffix="where">
<if test="name!=null">name=#{name},</if>
<if test="age!=null">age=#{age},</if>
<if test="dept.id!=null">deptId=#{dept.id},</if>
</trim>
id=#{id}
</update>
choose
? 类似if,可用otherwise
<select id="getEmpList" parameterType="Employee" resultMap="EmpMap">
SELECT emp.id,emp.name ename,emp.age,d.name dname,a.account
FROM employee emp LEFT JOIN dept d ON emp.deptId=d.id
LEFT JOIN admin a ON emp.adminId=a.id
<trim prefix="where" prefixOverrides="and||or">
<choose>
<when test="name!=null & name !='' ">
and emp.name=#{name}
</when>
<otherwise>
and emp.name='abc'
</otherwise>
</choose>
<choose>
<when test="age!=null & age !='' ">
and emp.age=#{age}
</when>
</choose>
</trim>
</select>
set
? 动态添加set关键字,删掉最后一个逗号
动态修改:传入的为空不改,不为空就修改
@Test
public void updateEmp(){
Employee employee1 = new Employee();
employee1.setId(1);
employee1.setName("abc");
employee1.setAge(20);
Dept dept = new Dept();
dept.setId(2);
employee1.setDept(dept);
SqlSession sqlSession = MybatisUtil.openSession();
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
employeeMapper.updateEmp(employee1);
sqlSession.commit();
sqlSession.close();
}
public interface EmployeeMapper {
void updateEmp(Employee employee);
}
<update id="updateEmp" parameterType="Employee">
update employee
<set>
<if test="name!=null">name=#{name},</if>
<if test="age!=null">age=#{age},</if>
<if test="dept.id!=null">deptId=#{dept.id},</if>
</set>
where id=#{id}
</update>
foreach
? 通过多个age查询时,就要对List中的元素进行遍历
在sql语句中是这样写的:
SELECT * FROM employee WHERE age IN(19,20)
所以使用foreach遍历时,就可以设定前缀、后缀和分隔符
- item 表示集合中每一个元素进行迭代时的别名
- index 指定一个名字,用于表示在迭代过程中,每次迭代到的位置
- open 表示该语句以什么开始
- separator 表示在每次进行迭代之间以什么符号作为分隔符
- close 表示以什么结束
- collection 指定传来的类型(array、list …)
@Test
public void getEmpByAges(){
List<Integer> ageList = new ArrayList<>();
ageList.add(18);
ageList.add(19);
SqlSession sqlSession = MybatisUtil.openSession();
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
List<Employee> employees = employeeMapper.getEmpByAges(ageList);
System.out.println(employees);
sqlSession.commit();
sqlSession.close();
}
public interface EmployeeMapper {
List<Employee> getEmpByAges(List<Integer> ages);
}
<select id="getEmpByAges" resultType="Employee">
select * from employee
where age in
<foreach item="age" collection="list" open="(" separator="," close=")">
#{age}
</foreach>
</select>
特殊符号
使用案例:通过年龄段查询
@Test
public void getEmpByAgeInterval(){
SqlSession sqlSession = MybatisUtil.openSession();
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
List<Employee> employees = employeeMapper.getEmpByAgeInterval(18,20);
System.out.println(employees);
sqlSession.commit();
sqlSession.close();
}
public interface EmployeeMapper {
List<Employee> getEmpByAgeInterval(@Param("min")int min,@Param("max")int max);
}
<select id="getEmpByAgeInterval" resultType="Employee">
select * from employee
where age > #{min} and age < #{max}
</select>
缓存
- 缓存(cache)的作用是为了减去数据库的压力,提高数据库的性能。
- 缓存实现的原理是从数据库中查询出来的对象在使用完后不要销毁,而是存储在内存(缓存)中,当再次需要获取该对象时,直接从内存(缓存)中直接获取,不再向数据库执行 select 语句,从而减少了对数据库的查询次数,因此提高了数据库的性能。
- 缓存是使用 Map 集合缓存数据的
一级缓存
? 一级缓存的作用域是同一个 SqlSession,在同一个 sqlSession 中两次执行相同的 sql 语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查 询,从而提高查询效率。当一个 sqlSession 结束后该 sqlSession 中的一级缓存也就不存在了。Mybatis 默认开启一级缓存
生命周期:
- MyBatis 在开启一个数据库会话时,会创建一个新的 SqlSession 对象,SqlSession 对象中会有一个新的 Executor 对象。Executor 对象中持有一个新的 PerpetualCache 对象,如果 SqlSession 调用了 **close()**方法,会释放掉一级缓存 PerpetualCache 对象,一级缓存将不可用。
- 如果 SqlSession 调用了 clearCache(),会清空 PerpetualCache 对象中的数据,但是该对象仍可使用。
- SqlSession 中执行了任何一个 update 操作(update()、delete()、insert()) ,都会清空 PerpetualCache 对象的数据,但是该对象可以继续使用
? 一级缓存时执行commit,close,增删改等操作,就会清空当前的一级缓存;当对SqlSession执行更新操作(update、delete、insert)后并执行commit时,不仅清空其自身的一级缓存(执行更新操作的效果),也清空二级缓存(执行commit()的效果)
二级缓存
? 二级缓存是 SqlSessionFactory 级别的,根据 mapper 的 namespace 划分区域的,相同 namespace 的 mapper 查询的数据缓存在同一个区域,如果使用mapper 代理方法每个 mapper 的 namespace 都不同,此时可以理解为二级缓存区域是根据 mapper 划分
? 每次查询会先从缓存区域查找,如果找不到则从数据库查询,并将查询到数据写入缓存。Mybatis 内部存储缓存使用一个 HashMap,key 为hashCode+sqlId+Sql 语句。value 为从查询出来映射生成的 java 对象。
? sqlSession 执行 insert、update、delete 等操作 commit 提交后会清空缓存区域,防止脏读
配置:
-
开启全局二级缓存配置,在mybatis-config.xml下加设置 <setting name="cacheEnabled" value="true"/>
-
POJO 序列化 :将所有的 POJO ()类实现序列化接口 Java.io. Serializable -
配置映射文件 在 Mapper 映射文件中添加 cache,表示此 mapper 开启二级缓存。 当 SqlSeesion 关闭时,会将数据存入到二级缓存
<cache flushInterval="5000"/>
-
禁用缓存 如测试sql语句性能时缓存会影响测试准确性 需要禁用在映射文件中:默认值是true useCache=”false” -
刷新缓存 在映射文件中:属性:flushCache=”true”刷新缓存,在查询语句中,默认值是false,在新增删除修改语句中,默认值是true(清空缓存)
|