一、框架概述
1.1 什么是框架
框架(Framework)是整个或部分系统的可重用设计表现为一组抽象构件及构件实例间交互的方法另一种定义认为,框架是可被应用开发者定制的应用骨架。
前者是从应用方面而后者是从目的方面给出的定义。简而言之,框架其实就是某种应用的半成品,就是一组组件,供你选用完成你自己的系统。简单说就是使用别人搭好的舞台,你来做表演。而且,框架一般是成熟的,不断升级的软件。
1.2 框架要解决的问题
框架要解决的最重要的一个问题是技术整合的问题,在J2EE的 框架中,有着各种各样的技术,不同的软件企业需要从J2EE 中选择不同的技术,这就使得软件企业最终的应用依赖于这些技术,技术自身的复杂性和技术的风险性将会直接对应用造成冲击,而应用是软件企业的核心,是竞争力的关键所在,因此应该将应用自身的设计和具体的 实现技术解耦。
这样,软件企业的研发将集中在应用的设计上,而不是具体的技术实现,技术实现是应用的底层支撑,它不应该直接对应用产生影响。
框架一般处在低层应用平台 (如12EE) 和高层业务逻辑之间的中间层。
1.3 软件开发的分层重要性
框架的重要性在于它实现了部分功能,并且能够很好的将低层应用平台和高层业务逻辑进行了缓和。为了实现 软件工程中的"高内聚、低合"。把问题划分开来各个解决,易于控制,易于延展,易于分配资源。我们常项的 MVC 软件设计思想就是很好的分层思想。
1.4 分层开发下的常见框架
常见的JavaEE开发框架:
解决数据的持久化问题的框架
MyBatis
MyBatis 本是apache的一个开源项目Batis,2010年这个项目由apache software foundation 迁移到了googlecode,并且改名为MyBatis。2013年11月迁移到Github。
iBATIS一词来源于“intemer和abatis”的组合,是一个基于Java的持久层框架。BATIS提供的持久层框架包括SQLMaps和DataAccess Obiects (DAOs)
作为持久层的框架,还有一个封装程度更高的框架就是Hibernate,但这个框架因为各种原因目前在国内的流行程度下降大多,现在公司开发也越来越少使用。目前使用Spring Data来实现数据持久化也是一种趋势。
2、解决WEB层问题的MVC框架
spring MVC
Spring MVC属于SpringFrameWork的后续产品,已经融合在Sprng Web Flow里面。Spring 框架提供了构建Web 应用程序的全功能MVC模块。使用Spring可插入的MVC 架构,从而在使用Spring进行WEB开发时,可以选择使用Spring的SpringlVC框架或集成其他MVC开发框架,如Struts1(现在一般不用),Struts2等。
3、解决技术整合问题的框架
spring框架
Spring挺架是由于软件开发的复杂性而创建的。Sprina使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。然而Spring的用途不仅仅限于服务器端的开发。从简单性、可测试性和松合性角度而言,绝大部分Java应用都可以从Spring中受益。
Spring是一个轻量级控制反转(loC)和面向切面(AOP)的容器框架。
二、MyBatis简介
2.1 什么是MyBatis?
MyBatis是一个开源轻量级的数据持久化框架,是]DBC和Hibernate的替代方案
MyBatis 前身为 Batis,2002年由 Clinton Begin 发布。2010 年从 Apache 迁移到 Google,并改名为 MyBatis,2013 年又迁移到了Github。
mybatis是一个优秀的基于java 的持久层框架,它内部封装了jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement 等繁杂的过程。
mybatis 通过 xml或注解的方式将要执行的各种 statement 配置起来,并通过aa 对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。
采用ORM思想解决了实体和数据库映射的问题,对 db进行了封装,屏蔽了dbcapi底层访问细节,使我们不用与 jdbc api打交道,就可以完成对数据库的持久化操作。
MyBatis 支持定制化SQL、存储过程以及高级映射可以在实体类和SQL语句之间建立映射关系,是一种半自动化的ORM实现。
MyBatis 的中文官网: https://mybatis.org/mybatis-3/zh/index.html
2.2 为什么要使用MyBatis?
MyBatis主要的目的就是简化JDBC操作,并且满足高并发和高响应的要求
分析以上JDBC存在的问题:
1.数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题2.Sql语句在代码中硬编码,造成代码不易维护,实际应用sql 变化的可能较大,sql变动需要改变java
代码。
3.使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定可能
多也可能少,修改sql还要修改代码,系统不易维护
4对结果集解析存在硬编码(查询列名),sql 变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便。
再次回顾mybatis特点:
mybatis内部封装了jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement等繁杂的过程。
mybatis通过xml或注解的方式将要执行的各种statement 配置起来,并通过java对象和statement 中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。
采用ORM思想解决了实体和数据库映射的问题,对jdbc进行了封装,屏蔽了jdbc api底层访问细节,使我们不用与jdbc api打交道,就可以完成对数据库的持久化操作。
三、MyBatis入门案例
下面我们来用一个入门的案例,了解一下MyBatis的基本操作步骤和使用方法。
创建数据库和数据表
#创建表
CREATE TABLE user(
uid int PRIMARY KEY AUTO_INCREMENT,
uname VARCHAR(32),
password VARCHAR(32),
phone VARCHAR(11),
address VARCHAR(64)
);
INSERT INTO user(uname,password,phone,address) VALUES('张三','666','18965423548','南阳');
INSERT INTO user(uname,password,phone,address) VALUES('李四','333','18754263548','许昌');
INSERT INTO user(uname,password,phone,address) VALUES('小美','123','18565234759','信阳');
创建一个Java项目并导入mybatis框架的jar包
在项目中创建一个lib文件夹放入mybatis框架的jar包,并导入项目中。
创建跟表对应的实体类。
在src中创建com.chen.bean包,然后创建User实体类
创建一个DelVO实体类里面放一个批量删除集合
package com.chen.bean;
import java.util.ArrayList;
import java.util.List;
public class DelVO {
List<Integer> ids = new ArrayList<>();
public List<Integer> getIds() {
return ids;
}
public void setIds(List<Integer> ids) {
this.ids = ids;
}
@Override
public String toString() {
return "DelVO{" +
"ids=" + ids +
'}';
}
}
package com.chen.bean;
public class User {
private Integer uid;
private String uname;
private String password;
private String phone;
private String address;
public User() {
}
public User(Integer uid, String uname, String password, String phone, String address) {
this.uid = uid;
this.uname = uname;
this.password = password;
this.phone = phone;
this.address = address;
}
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"uid=" + uid +
", uname='" + uname + '\'' +
", password='" + password + '\'' +
", phone='" + phone + '\'' +
", address='" + address + '\'' +
'}';
}
}
创建针对表操作的接口类。
在src中创建com.chen.dao包,然后创建UserDao的接口,然后在接口中定义针对数据库的增删
改查等操作。
package com.chen.dao;
import com.chen.bean.User;
import java.util.List;
public interface UserDao {
/**
* 查询所有用户信息
* @return
*/
List<User> selectAll();
/**
* 根据id查询用户信息
* @param uid
* @return
*/
User selectByuid(int uid);
/**
* 模糊查询
* @return
* @param s
*/
List<User> seach(User user);
/**
* 添加用户信息
* @param user
* @return
*/
int add(User user);
/**
* 删除用户信息
* @param uid
* @return
*/
int delete(int uid);
/**
* 修改用户信息
* @param user
* @return
*/
int update(User user);
//根据deLVO类封装的id集合进行批量删除
int deleteByIds(DelVO delVO);
}
在接口的包中创建对应的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接口-->
<mapper namespace="com.chen.dao.UserDao">
<!--通过select标签进行查询
id:映射接口的方法名
parameterType:指定参数的类型(如果是集合类型只需要指定集合元素的类型即可)
resultType:指定返回值的类型
-->
<select id="selectAll" resultType="com.chen.bean.User">
select * from user;
</select>
<select id="selectByuid" parameterType="int" resultType="com.chen.bean.User">
select * from user where uid=#{uid};
</select>
<insert id="add" parameterType="com.chen.bean.User">
insert into user (uname,password,phone,address) values(#{uname},#{password},#{phone},#{address});
</insert>
<delete id="delete" parameterType="int">
delete from user where uid = #{uid};
</delete>
<update id="update" parameterType="user">
update user
<set>
<if test="uname !=null and uname !=''">
uname=#{uname},
</if>
<if test="phone !=null and phone !=''">
phone=#{phone},
</if>
<if test="address !=null and address !=''">
address=#{address},
</if>
</set>
where uid = #{uid}
</update>
<select id="seach" parameterType="user" resultType="user">
select * from user
<where>
<if test="uname !=null and uname !=''">
and uname like concat('%',#{uname},'%')
</if>
<if test="phone !=null and phone !=''">
and phone like concat('%',#{phone},'%')
</if>
<if test="address !=null and address !=''">
and address like concat('%',#{address},'%')
</if>
</where>
</select>
<!--foreach表示对集合进行你迭代
collection:对象中的集合属性名称
item:迭代时的临时变量名
open:开始的字符
close:结束的字符
separator:分隔符
要对delVO这个对象中的ids属性 进行迭代,跌出来的临时变量叫id
然后配上开始结束、分隔符,把id的值输出进去
-->
<delete id="deleteByIds" parameterType="delVO">
delete from user where uid in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</delete>
</mapper>
在src目录中创建mybatis框架的核心配置文件。
在src中创建一个文件,命名为SqlMapConfig.xml,在该配置文件中配置连接数据库的参数。
jdbc.properties
log4j.properties
<?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="jdbc.properties"/>
<!--配置mybatis的运行:日志的输出-->
<settings>
<setting name="logImpl" value="log4j"/>
</settings>
<!--给实体类起别名-->
<typeAliases>
<package name="com.chen.bean"/>
</typeAliases>
<!--配置环境信息===就是配置连接数据库的参数
default:指定配置的环境信息的id,表示默认连接该环境
-->
<environments default="mysql">
<environment id="mysql">
<!--配置事务的处理方式:模式使用JDBC的事务处理-->
<transactionManager type="JDBC"></transactionManager>
<!--数据源的默认type设置为pooled,表示使用连接池-->
<dataSource type="pooled">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!--加载mapper配置文件-->
<mappers>
<package name="com.chen.dao"/>
</mappers>
</configuration>
7.在测试类中进行测试
使用mybatis框架需要按照框架的步骤进行
@Before
前置通知, 在目标方法(切入点)执行之前执行。
value 属性绑定通知的切入点表达式,可以关联切入点声明,也可以直接设置切入点表达式
注意:如果在此回调方法中抛出异常,则目标方法不会再执行,会继续执行后置通知 -> 异常通知。
@After 后置通知, 在目标方法(切入点)执行之后执行
package com.chen.test;
import com.chen.bean.User;
import com.chen.dao.UserDao;
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 UserTest {
InputStream stream = null;
SqlSessionFactoryBuilder builder = null;
SqlSessionFactory factory = null;
SqlSession sqlSession = null;
UserDao userDao = null;
@Before
public void init() throws IOException {
//1.创建字节输入流映射核心配置文件
stream = Resources.getResourceAsStream("mybatis.xml");
//2.创建 SqlSessionFactoryBuilder 对象 ---mybatis框架使用了建构者模式
builder = new SqlSessionFactoryBuilder();
//3.使用建构者模式创建工厂对象
factory = builder.build(stream);
//4.使用工厂对象获取sqlsession对象
sqlSession = factory.openSession();
//5.使用sqlsession对象创建userdao接口的代理对象
userDao = sqlSession.getMapper(UserDao.class);
}
@Test
public void select() throws IOException {
List<User> userList = userDao.selectAll();
for (User user : userList) {
System.out.println(user);
}
}
@Test
public void selectByuid() throws IOException {
User user = userDao.selectByuid(12);
System.out.println(user);
}
@Test
public void add() throws IOException {
User user = new User();
user.setUname("小美");
user.setPassword("123");
user.setPhone("13672834567");
user.setAddress("信阳");
int add = userDao.add(user);
if (add > 0) {
System.out.println("添加成功");
} else {
System.out.println("添加失败");
}
}
@Test
public void delete() throws IOException {
int delete = userDao.delete(12);
// int delete1 = sqlSession.delete("delete",15);
if (delete > 0) {
System.out.println("删除成功");
} else {
System.out.println("删除失败");
}
}
@Test
public void testseach() {
User seach = new User();
seach.setAddress("信阳");
List<User> userList = userDao.seach(seach);
for (User user : userList) {
System.out.println(user);
}
}
@Test
public void testdelete() {
List<Integer> ids = new ArrayList<>();
ids.add(4);
ids.add(5);
ids.add(6);
ids.add(7);
DelVO delVO = new DelVO();
delVO.setIds(ids);
userDao.deleteByIds(delVO);
}
@After
public void distroy() throws IOException {
sqlSession.commit();
sqlSession.close();
stream.close();
}
}
运行结果如下:
查询所有用户信息:
根据id查询用户信息:
根据姓名模糊查询:
添加用户信息:
删除用户信息:
修改用户信息:
批量删除:
删除17、18、19
四、MyBatis核心对象
从上面的案例中我们可以发现MyBatis一些核心要素:
核心接口和类
MyBatis核心配置文件(SqlMapConfigxml)
SQL映射文件(xxmapper.xml)
下面首先介绍MyBatis的核心接口和类,如下所示。
每个MyBatis应用程序都以一个SqlSessionFactory对象的实例为核心。
首先获取SqlSessionFactoryBuilder对象,可以根据XML配置文件或者Configuration类的实例构建该对象。
然后获取SqlSessionFactory对象,该对象实例可以通过SqlSessionFactoryBuilder 对象来获取。
有了SalSessionFactory对象之后,就可以进而获取 SqSession 实例。SalSession 对象中完全包含以数据库为背景的所有执行SQL操作的方法,用该实例可以直接执行已映射的SOL语句。
4.1 SqlSessionFactoryBuilder
SqlSessionFactoryBuilder 会根据配置信息或者代码生成SqlSessionFactory,并且提供了多个build()方法重载。
配置信息可以以三种形式提供给SqlSessionFactoryBuilder的 build0 方法分别是nputStream(字节流)、Reader (字符流)、Configuration (类)。
由于字节流和字符流都属于读取配置文件的方式,所以就很容易想到构建一个SSessionFactory有两种方式,即:读取XML配置文件和编写代码。
一般习惯为采取XML配置文件的方式来构造SalSessionFactory,这样一方面可以避免硬编码另一方面方便日后配置人员修改,避免重复编译代码。
SqlSessionFactoryBuilder的生命周期和作用域
SqlSessionFactoryBuilder 的最大特点就是用过即丢。创建SqlSessionFactory对象之后,这个类就不存在了,因此SqlSessionFactoryBuilder的最佳范围就是存在于方法体内,也就是局部变量。
4.2 SqlSessionFactory
SqlSessionFactory是工厂接口而不是现实类,他的任务就是创建 SqlSession。所有的 MyBatis 应用都以 SqSessionFactory 实例为中心,SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 对象来获取。有了它以后就可以通过 SqlSession 提供的openSession()方法来获取SqlSession实例。
SqlSessionFactory的生命周期和作用域
SqlSessionFactory对象一旦创建,就会在整个应用程序过程中始终存在,没有理由去销毁或再创建它,并且在应用程序运行中也不建议多次创建 SqlSessionFactory。因此 SqSessionFactory的最佳作用域是Application,即随着应用程序的生命周期-一直存在这种存在于整个应用运行期间,并且只存在一个对象实例”的模式就是所谓的单例模式。
4.3 SqlSession
SqlSession 是用于执行持久化操作的对象,类似于DBC 中的Connection。它提供了面向数据库执行 SQL命令所需的所有方法,可以通过SqlSession实例直接运行已映射的SQL语句。
SglSession的用途主要有两种
1.获取映射器。让映射器通过命名空间和方法名称找到对应的SQL,并发送给数据库,执行后返回结果。
2.直接通过“命名空间(namespace)+SQLid”的方式执行SQL,不需要获取映射器
SqlSession生命周期和作用域。
SqlSession生命周期和作用域
SqlSession 对应一次数据库会话。由于数据库会话不是永久的,因此SqlSession 的生命周期也不是永久的,每次访问数据库时都需要创建SqlSession对象。
需要注意的是:每个线程都有自己的 SqlSession 实例,qSesion 实例不能被共享,也不是线程安全的。因此 SqlSession 的作用域范围是request作用域或方法体作用域内。