1、项目的三层架构
包括:界面层(User Interface Layer)、业务逻辑层(Business Logic Layer)、数据访问层(Data Access Layer)
界面层:和用户打交道,接收用户请求参数,显示处理结果(包含如jsp、html、servlet等)
业务逻辑层:接收界面层传递的数据,计算逻辑,调用数据库,获取数据
数据访问层:访问数据库,执行对数据的查询,修改,删除等。
--三层中在项目目录中对应创建的包:
界面层(Controller层):controller包,servlet
业务逻辑层(Service层):service包,XXXService类
数据访问层(Dao层):dao包,XXXDao类
--三层中java类的交互
用户使用界面层->业务逻辑层->数据访问层(持久层)->数据库(MySQL)
--具体实现过程:
用户在界面发起请求,界面层接收请求数据传递给业务逻辑层,
逻辑层根据请求进行业务处理,需要使用到数据库,
然后访问到持久层,持久层连接数据库,数据库根据需求返回数据给持久层,
持久层又把数据给业务逻辑层,业务逻辑层拿到数据进行加工,最后把结果通过界面层展示给用户
--三层需要的对应处理框架
界面层(Controller层)-servlet-springMVC(框架)
业务逻辑层(Service层)-Service类-spring(框架)
数据访问层(Dao层)-Dao类-MyBatis(框架)
2、Maven项目使用Mybatis
2.1 实现步骤
在maven项目中使用mybatis实现查询数据库库中的一张表
步骤:
1、在对应目录中创建一个空工程:idea-maven-mybatis
2、添加新的maven模块:a-maven-mybatis01,保存在空工程的目录下
3、在pom文件中添加mybatis的依赖坐标和mysql的依赖坐标
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
4、创建实体类com.studymyself.entity.User
--作用是mybatis用来创建对象保存查询结果集中表中的一行数据
5、创建持久层的dao接口com.studymyself.dao.UserDao其中定义操作数据库的方法
--mybatis是在持久层起作用的
6、在dao包下创建sql映射文件--一般一个表对应一个映射文件,文件名和接口名一样
由于该sql映射文件不在resources目录中,所以要在pom文件中配置资源位置的插件信息,使该文件编译时拷贝到
target对应目录中
<build>
<resources>
<resource>
<!--表示编译时在src/main/java目录下的-->
<directory>src/main/java</directory>
<!--所有.properties、.xml类型的文件可以被扫描到-->
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<!--filtering的值false表示不启用过滤器,上面include已经是过滤操作了-->
<filtering>false</filtering>
</resource>
</resources>
</build>
7、在resources目录中创建mybatis核心目录文件
8、在resources目录下创建保存数据库连接信息的属性文件--jdbc.properties
9、创建使用的mybatis类,通过mybatis访问数据库的代码。--在studymyself包下创建MyApp.java
2.2 错误分析
--首先我们要知道的是什么叫项目的类的根路径:
maven项目中,点击右边的maven会出现maven窗口,双击其中的compile,开始项目的编译,而后生成一个target文件目录。
打开target里面是一个classes目录,存放的是编译后的class文件和resources目录中的资源配置文件。
当我们不是maven项目时,直接运行项目,也是先编译后运行,也会生成target目录,和上面一样。
所以运行时JVM是到classes目录中找class文件和配置资源文件来运行的,
这样就可以理解为target\classes目录就是项目类的根路径,对应的是项目中的src目录,
在maven项目中src目录和src\main是相当于一样一个src目录,
所以maven项目中resources目录中的文件被拷贝到类的根路径下,
而java文件的class文件存放在原先一样的层次包中,
只不过是根目录换了个名字而已,项目的目录结构还是没变,resources中被提取出来了。
就是说:
maven项目中存放在resources目录中的资源配置文件,编译后存到的是类根路径下,
所以上面第八步中代码的获取mybatis的核心配置文件时该文件的路径只需填文件名加文件类型就可以了。
而sql映射文件通过资源插件的配置拷贝到的是target/classes/com/studymyself/dao目录中,
mybatis是从根路径中扫描文件,所以在mybatis核心配置文件中配置sql映射文件的位置信息就是这样:
com/studymyself/dao/UserDao.xml
--注意是左斜杠,不是点,还要保证resources目录一定要是资源文件夹
有时候我们的项目配置都没有问题,但生成的target目录中不一定有相关的文件,
我们就得clean,然后再编译。
或者直接Rebuild Project。
或者手工复制资源配置文件到target对应目录中。
或者File->Invalidata Caches/Restart,表示删除idea之前项目的缓存,重启idea
2.3 在上面的基础上增加添加数据的方法测试
2.4 首先在UserDao增加添加数据的方法
package com.studymyself.dao;
import com.studymyself.entity.User;
import java.util.List;
//接口操作t_user表
public interface UserDao {
//该方法查询t_user表中所有数据
public List<User> getAll();
//添加数据的方法,要和映射文件中sql语句的id一致,namespace的值也要是本接口的全限定名
//才能将方法和sql映射文件中的某个id的sql语句绑定,而方法中传递的参数就和sql语句的parameterType属性
//的作用一样
public int addUser(User user);
}
2.5 添加sql语句
<?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">
<!--命名空间属性:里面的值是dao接口的全限定名,虽然可以自定义字符串,但不能这样用-->
<!--作用是区分有多个sql映射文件时,下面sql语句的唯一标识id可能重复,
这时就用接口全限定名.id 来区别执行哪个映射文件的同id的sql语句了
com.studymyself.dao.UserDao.getAll
-->
<mapper namespace="com.studymyself.dao.UserDao">
<!--id中的值是这条sql语句在本映射文件中的唯一标识
而com.studymyself.dao.UserDao.getAll是这条sql语句在本项目中的唯一标识
-->
<select id="getAll" resultType="com.studymyself.entity.User">
select
id,loginName,loginPwd,realName
from
t_user
</select>
<!--下面需要传参数,但是我们并没有设置parameterType属性却仍然可以执行添加数据的操作
原因是namespace属性绑定的UserDao接口中添加数据的方法addUser需要的参数是User对象
本句sql语句的id和接口中的方法名一致,所以下面占位符中传的值就是接口方法中传递
的User对象中的值,也就不在多余设置parameterType属性为User类了,但是还是写上更好
-->
<insert id="addUser">
insert into t_user
(loginName,loginPwd,realName)
values
(#{loginName},#{loginPwd},#{realName})
</insert>
</mapper>
2.6 核心配置文件添加一个全局配置,添加输出控制台日志的配置
<?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全局行为,
顺序要在上面的properties属性下面-->
<settings>
<!--设置mybatis输出日志-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--environments是环境配置,是复数说明下面可以配置多个环境
所以default属性的值表示这个配置文件用下面的哪一套数据库配置信息
这里使用development1
-->
<environments default="development1">
<!--第一套数据库连接信息配置,唯一标识id叫development-->
<environment id="development1">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<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>
<!--第二套数据库连接信息配置,唯一标识id叫development-->
<environment id="development2">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<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>
<!--这里也可以添加多个sql映射文件,因为项目中不止一个表
这就是映射文件中namespace属性的作用了-->
<!-- <mapper resource="StudentDao.xml"/>-->
<mapper resource="com/studymyself/dao/UserDao.xml"/>
</mappers>
</configuration>
2.7 数据库信息的属性配置文件如下
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mysqlstudy
jdbc.username=root
jdbc.password=rong195302
2.8 pom文件
<?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>com.studymyself</groupId>
<artifactId>a-maven-mybatis01</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<!--表示编译时在src/main/java目录下的-->
<directory>src/main/java</directory>
<!--所有.properties、.xml类型的文件可以被扫描到-->
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<!--filtering的值false表示不启用过滤器,上面include已经是过滤操作了-->
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
2.9 在test目录中新建mabatis测试类com.studymyself.TestMyApp
package com.studymyself;
import com.studymyself.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.Test;
import java.io.IOException;
public class TestMyApp {
@Test
public void testAddSql(){
SqlSession sqlSession = null;
try {
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis.xml"));
sqlSession = sqlSessionFactory.openSession();
//将UserDao.xml文件中namespace的值放进一个变量中
String userId = "com.studymyself.dao.UserDao";
//插入数据
User user1 = new User();
user1.setLoginName("12306");
user1.setLoginPwd("sss123456789");
user1.setRealName("铁路总局");
int count = sqlSession.insert(userId+".addUser",user1);
System.out.println("添加到数据库记录条数:"+count);
sqlSession.commit();
} catch (IOException e) {
if (sqlSession != null) {
sqlSession.rollback();
}
e.printStackTrace();
}finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
}
2.10 执行测试方法,控制台输出的日志和结果信息如下:
2.11 执行查询的输出信息如下
3、创建Mybatis的工具类
3.1 mybatis使用的对象的生命周期
--我们需要知道SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession三个对象的生命周期
--SqlSessionFactoryBuilder对象:
官方文档原话:
--这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。
因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,
但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
--这个类是用来创建获取SqlSessionFactory对象的,SqlSessionFactory对象一旦被创建SqlSessionFactoryBuilder就没有用了,可以被垃圾回收器回收销毁了。从官方例子中可以直接看出:
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
从上面官方文档给的代码就知道:
new出来的SqlSessionFactoryBuilder的对象用完build方法创建SqlSessionFactory对象后就被垃圾回收器回收了
因为没有引用变量指向这个对象:SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
而sqlSessionFactory指向的是SqlSessionFactory对象
--SqlSessionFactory对象:
官方文档原话:
--SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
--很明显该类对象是非常重要的,一个项目只能有一次创建,并且不能随意丢弃和重新创建另一个
如果放在一个工具类里的话,创建SqlSessionFactory对象的语句块需要放到静态语句块中,在项目加载时创建该对象,只执行一次。
SqlSessionFactory是一个接口:--实现类是 DefaultSqlSessionFactory
作用:获取SqlSession对象 SqlSession sqlSession =sqlSessionFactory.opensession();
--openSession()方法说明:
openSession():方法中无需参数,获取sqlSession的时候会关闭自动提交事务机制
openSession(boolean):参数是布尔类型,true表示获取sqlSession的时候会开启自动提交事务机制
false表示获取sqlSession的时候会关闭自动提交事务机制
--SqlSession对象
SqlSessin也是一个接口,该接口中定义了许多操作数据的方法:实现类 DefaultSqlSession
官方文档原话:
--每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。
意思就是:
SqlSession对象不是线程安全的,需要在方法的内部使用,即在一个方法中创建该对象(在执行sql语句前获取SqlSession对象:SqlSession session = sqlSessionFactory.openSession();),使用完毕后关闭sqlsession.close()
每个线程都应该有它自己的 SqlSession 实例,所以一次请求对应一个SqlSession对象,就是说一个请求中不能被创建两个及以上的SqlSession对象,一个线程对应一次事务。不能被共享,就是说该对象不能定义为静态的。
如果在工具类中,确保一次请求只能创建一个SqlSession对象,就是说无论怎么使用openSession方法,创建的都是同一个SqlSession对象,需要把该对象存到ThreadLocal对象local中(这是一个集合类对象),每次调用openSession方法前先判断local中是否有SqlSession对象(local.get()==null),为null,则执行local.set(sqlSession),然后sqlSession = local.get(),否者直接返回sqlSession。
还需要注意的一点是,在写工具类的关闭SqlSession资源时,除了关闭sqlsession.close()外,还需要将执行local.remove()。理由是执行local.set(sqlSession)方法时,当前这个线程就绑定了local集合,因为Tomcat中存在一个"线程池",线程结束后是存到该线程池中的,线程结束后如果不移除local集合中的SqlSession对象,下次该线程可能被使用到的时候会继续使用上次的local,也就继续使用上一个SqlSession对象,但是由于上一个SqlSession对象执行了close方法,继续使用就会报错。
3.2 创建mybatis工具类
一个mybatis工具类:com.studymyself.utils.MyBatisUtil
package com.studymyself.utils;
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;
public class MyBarisUtil {
//需要在外边定义,下面的代码好使用
private static SqlSessionFactory sqlSessionFactory = null;
//SqlSessionFactory对象只能被创建一次
static {
//SqlSessionFactoryBuilder对象只是为了创建SqlSessionFactory对象,所以不需要引用指向
//SqlSessionFactoryBuilder对象,代码执行完毕该对象就被回收了
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis.xml"));
} catch (IOException e) {
e.printStackTrace();
}
}
//定义一个集合存储SqlSession对象,确保一次事务(线程、请求)只创建一个
private static ThreadLocal<SqlSession> local = new ThreadLocal<>();
/**
* 获取当前线程的SqlSession对象
* @return
*/
public static SqlSession getSqlSession(){
//先获取local集合中的SqlSession
SqlSession sqlSession = local.get();
if (sqlSession == null) {
sqlSession = sqlSessionFactory.openSession();
local.set(sqlSession);
}
return sqlSession;
}
/**
* 关闭资源以及移除集合中的sqlSession对象
*/
public static void close(SqlSession sqlSession){
if (sqlSession != null) {
sqlSession.close();
local.remove();
}
}
}