概述
Spring 是最受欢迎的企业级 Java 应用程序开发框架
Spring 框架来创建性能好、易于测试、可重用的代码
Spring 框架是一个开源的 Java 平台
Spring 是轻量级的框架,其基础版本只有 2 MB 左右的大小
Spring 框架的核心特性是可以用于开发任何 Java 应用程序,但是在 Java EE 平台上构建 web 应用
程序是需要扩展的。
特性
非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API
*控制反转:IOC——Inversion of Control,指的是将对象的创建权交给 Spring 去创建。使用
Spring 之前,对象的创建都是由我们自己在代码中new创建。而使用 Spring 之后。对象的创
建都是由给了 Spring 框架。
*依赖注入:DI——Dependency Injection,是指依赖的对象不需要手动调用 setXX 方法去设
置,而是通过配置赋值。
*面向切面编程:Aspect Oriented Programming——AOP
容器:Spring 是一个容器,因为它包含并且管理应用对象的生命周期
组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用
XML和Java注解组合这些对象。
一站式:在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库
IOC不是技术,是一种设计思想。把创建对象以及管理对象之间关系的权力,交给第三方容器(Spring容 器),当容器加载时,自动创建对象,完善对象之间的关系;
解析xml的方式:dom4j
xml头信息:schema/dtd
1)用于描述该文档的基本信息。
2)规定标签的种类,不能写自定义标签
3)规定标签的书写个数
4)规定标签的书写顺序
5)提醒功能
1、Spring原理
原理分析:
分析:通过反射获取对象
反射获取对象需要什么?
类的全路径:com.soft.xxx.xx
类的全路径怎么去管理?
通过xml的形式管理
在Spring中,把对象成为bean,每一个bean就是一个对象
单个bean:对象的路径、对象的属性、属性对应的值
【实例】
<beans>
<bean id="stu1" class="com.soft.entity.Student">
<property name="name" value="Tom"/>
<property name="age" value="18"/>
bean>
<bean id="stu2" class="com.soft.entity.Student">
<property name="name" value="Jack"/>
<property name="age" value="19"/>
bean>
beans>
2、DI(依赖注入)
导入依赖
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.2.9.RELEASEversion>
dependency>
会自动导入spring-core和spring-beans等其他依赖
(1)Setter注入
给注入对象生成set方法
创建xml文件,描述所有需要管理的对象,配置对象之间的依赖关系
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用bean描述一个对象-->
<bean id="studentController" class="com.setter.controller.StudentController">
<!--
name:成员变量的set方法去掉set方法后,首字符小写;如果去掉set之后,前两个字母是
大写,就使用去掉set之后的内容;
ref:关联对象,bean的id的值
value:字面量
-->
<property name="studentService" ref="studentService"/>
</bean>
<bean id="studentService" class="com.setter.service.StudentService">
<property name="studentDao" ref="studentDao"/>
</bean>
<bean id="studentDao" class="com.setter.dao.StudentDao">
<property name="name" value="tom"/>
<property name="age" value="18"/>
<property name="sex" value="男"/>
</bean>
</beans>
测试
public class TestSetter {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beansetter.xml");
StudentController studentController =
context.getBean("studentController", StudentController.class);
studentController.login();
studentController.regist();
}
}
(2)构造器注入
给注入对象生成构造方法
创建xml文件,描述所有需要管理的对象,配置对象之间的依赖关系 Controll
bean.xml
xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="studentController" class="com.constructor.controller.StudentController">
<!--
ref:依赖对象,bean的id值
value:字面量
index:构造方法参数的索引,从0开始
name:构造方法参数的名称
type:构造方法参数的类型,全限定名,java.lang.Integer
-->
<constructor-arg name="studentService" ref="studentService"/>
</bean>
<bean id="studentService" class="com.constructor.service.StudentService">
<constructor-arg name="studentDao" ref="studentDao"/>
</bean>
<bean id="studentDao" class="com.constructor.dao.StudentDao">
<constructor-arg type="java.lang.String" value="tom"/>
<constructor-arg type="java.lang.String" value="男"/>
<constructor-arg type="java.lang.String" value="19"/>
</bean>
</beans>
(3)基于注解的注入(重点!)
在所有的输入对象上添加注解:@Component
作用是替换原来xml中的配置,可以理解为: , id值默认是类名首字母小写,也可以通过注解的value属性修改。 根据分层@Component注解可以拆分成三个注解,控制层:@Controller、业务层:@Service、持久 层:@Repository
在注入属性上添加注解:@Autowired或者@Resource
创建xml文件,开启注解开关
bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--
开启注解的扫描开关
base-package:扫描@Component注解所在的包
-->
<context:component-scan base-package="com.soft"/>
</beans>
@Autowired和@Resource的区别:
1、@Autowired默认根据类型注入对象,也可以搭配@Qualifier实现根据名称注入对象;
2、@Resource默认根据名称注入对象,找不到对应名称时,根据类型注入对象;一旦@Resource指定
了名称,必须按照名称匹配。
3、@Autowired是Spring的注解,@Resource是JDK的注解(推荐使用);
@Value:给字面量属性赋值
3、对象作用域
默认情况下,容器对每个bean元素只会创建一个实例对象,即单例模式:singleton;
如果将作用域设置为prototype,则每次调用getBean方法,就会创建一个新的对象,即多例模式:
prototype;
基于xml配置
基于注解配置
@Controller
//单例模式
@Scope("singleton")
public class StudentController{
String msg;
public String getMsg() {
System.out.println(this.msg);
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
@Controller
//多例模式
@Scope("prototype")
public class StudentController{
String msg;
public String getMsg() {
System.out.println(this.msg);
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
测试
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
StudentController controller1 = context.getBean("studentController",StudentController.class);
controller1.setMsg("对象");
controller1.getMsg();
StudentController controller2 = context.getBean("studentController",StudentController.class);
controller2.getMsg();
}
1、代理模式
代理模式 代理对象:增加额外功能之后的对象
被代理对象(目标对象):核心业务所在对象
静态代理:
1、要求有一个接口
2、目标对象、代理对象都要实现同一个接口
缺点:因为代理对象,需要与目标对象实现一样的接口。所以会有很多代理类,类太多。一旦接口增加
方法,目标对象与代理对象都要维护。
动态代理(JDK代理):JDK1.5增加的功能
动态代理(JDK代理):JDK1.5增加的功能
1、要求有一个接口
2、创建目标对象(实现接口)
3、创建 “创建代理对象的工厂
CGLIB代理
2、AOP的作用
AOP面向切面的思想,把核心业务与增强功能分开,降低模块之间的耦合度,形成了可插拔式的结构, 在保证核心业务执行不受影响的条件下,可以选择性地插入增强功能,达到扩展功能的目的。
3、AOP术语
连接点(joinpoint):什么时候添加额外的功能,方法开始前,方法执行后,方法报错了……
切点(pointcut):目标对象中的每一个方法都称为切点(切入点的集合)
切入点:具体的哪一个切点
增强/通知(advice):额外添加的那部分功能
before:前置通知,当方法开始执行时执行。
afterReturning:后置通知,当方法正常return时执行。
afterThrowing:异常通知,当方法发生异常时执行。
after:最终通知,当方法执行完毕之后执行,多用于释放资源。
around:环绕通知。
目标对象(Target):被代理对象,具有核心的业务
代理(Proxy):代理对象
切面(aspect):增强和切点表达式组成的内容
织入(weaver):把额外功能添加到目标对象的过程
切点表达式:函数(关键字)和表达式组成
语法结构:函数(表达式)
aop依赖的jar包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
4、基于XML开发AOP
(1)创建通知/增强类,编写增强(before、after、afterReturning、around、afterThrowing)
(2)通过XML配置增强类,把增强类交给Spring管理
(3)通过XML配置切面
xml配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--增强的业务逻辑类-->
<bean id="aspectXML" class="com.soft.aspect.AspectXML"/>
<!--AOP配置-->
<aop:config>
<!--
切面
ref:依赖增强类的id
order:执行顺序
-->
<aop:aspect ref="aspectXML" order="2">
<!--把切点表达式抽出来为公共的-->
<aop:pointcut id="logPointcut" expression="execution(*com.soft.service.*.*(..))"/>
<!--前置通知-->
<aop:before method="before" pointcut-ref="logPointcut"/>
<!--后置通知-->
<aop:after-returning method="afterReturning" pointcutref="logPointcut" returning="o"/>
<!--环绕通知-->
<aop:around method="around" pointcut-ref="logPointcut"/>
<!--异常通知-->
<aop:after-throwing method="afterThrowing" pointcutref="logPointcut" throwing="e"/>
<!--最终通知-->
<aop:after method="after" pointcut-ref="logPointcut"/>
</aop:aspect>
</aop:config>
</beans>
5、基于注解开发AOP
(1) 创建通知/增强类,编写增强(before、after、afterReturning、around、afterThrowing)
(2) 通过XML配置注解开关
package com.soft.aspect;
@Component // 把当前类交给Spring管理
@Aspect // 当前类是一个增强类
@Order(1) // 执行顺序,数值越小,优先级越高
public class AspectAnnotation {
@Pointcut("execution(* com.soft.dao.impl.*.*(..))")
public void pointCut(){
}
/**
* 前置通知
* 在方法执行之前添加通知
*/
@Before("pointCut()")
public void before(){
System.out.println("注解方式:前置通知");
}
/**
* 最终通知
* 在方法执行之后添加通知
*/
@After("pointCut()")
public void after(){
System.out.println("注解方式:最终通知");
}
/**
* 环绕通知
* 在方法执行之前,return时添加通知
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
@Around("pointCut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("注解方式:环绕通知----开始");
// 执行目标方法
Object proceed = proceedingJoinPoint.proceed();
System.out.println("注解方式:环绕通知----方法结束");
return proceed;
}
/**
* 后置通知
* 在方法return之后添加通知
* @param o
*/
@AfterReturning(value = "pointCut()", returning = "o")
public void afterReturning(Object o){
System.out.println("注解方式:后置通知");
}
**
* 异常通知
* 方法发生异常时添加通知,可以指定异常类型
*/
@AfterThrowing(value = "pointCut()", throwing = "e")
public void afterThrowing(Throwable e){
System.out.println("注解方式:异常通知");
}
}
xml文件配置AOP的注解开关
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--AOP注解的开关-->
<aop:aspectj-autoproxy/>
</beans>
6、切面通知的执行顺序
切面执行顺序总结
在不指定order排序的情况下:
1、xml配置按照配置的先后顺序执行
2、注解配置按照切面类类名的字典排序执行
3、xml和注解同时存在,xml优先执行
在指定order排序的情况下:
按照order给定的数值从小到大执行,数值越小,执行顺序越优先;
7、AOP的应用场景
事务、日志、权限控制等
1、导入数据库驱动
<dependency>
<groupId>ojdbc</groupId>
<artifactId>ojdbc</artifactId>
<version>6.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
2、Spring容器中配置数据库连接
3、配置JdbcTemplate,并且和数据连接关联
jdbc.properties文件
oracle.driver=oracle.jdbc.OracleDriver
oracle.url=jdbc:oracle:thin:@localhost:1521:orcl
oracle.user=user
oracle.pass=pass
SpringJDBC.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--加载properties文件-->
<context:property-placeholder location="classpath*:jdbc/jdbc.properties"/>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${oracle.driver}"/>
<property name="url" value="${oracle.url}"/>
<property name="username" value="${oracle.username}"/>
<property name="password" value="${oracle.password}"/>
</bean>
<!--
可以理解为构建SQL执行器
在Spring中叫JdbcTemplate,要通过JdbcTemplate执行SQL
执行SQL在Dao层,在Dao层有JdbcTemplate的属性
-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
4、在Dao层注入JdbcTemplate,调用方法
@Autowired
JdbcTemplate jdbcTemplate;
// 查询单条记录,返回值为Map
Map<String, Object> queryForMap(String sql, @Nullable Object... args)
// 查询多条记录,返回List
List<Map<String, Object>> queryForList(String sql, @Nullable Object... args)
RowMapper<T> rowMapper = new BeanPropertyRowMapper(Class<T> clazz);
// 查询返回单条记录
T jdbcTemplate.queryForObject(sql, rowMapper, args...);
// 查询多条记录
List<T> jdbcTemplate.query(sql, rowMapper);
List<T> jdbcTemplate.query(sql, rowMapper, args...);
// 插入、更新、删除,返回操作成功的记录数
jdbcTemplate.update(sql, args...);
数据库连接池负责分配、管理和释放数据库连接,
它允许应用程序重复使用一个现有的数据库连 接,而不是再重新建立一个;
释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数 据库连接而引起的数据库连接遗漏。
能明显提高对数据库操作的性能。
C3P0(开源)、DBCP(apache)、Druid(阿里)
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.uname}"/>
<property name="password" value="${jdbc.pwd}"/>
<!--连接池中最多支持多少个活动会话-->
<property name="maxActive" value="100"/>
<!--程序向连接池中请求连接时,超过maxWait的值后,认为本次请求失败,即连接池没有可用连接,单位毫秒,设置-1时表示无限等待-->
<property name="maxWait" value="5"/>
</bean>
<!--jdbc模板-相当于DBUtil-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
事务的四大特性:A(原子性)C(一致性)I(隔离性)D(持久性)
事务的隔离级别 :
读未提交:读到没有提交的数据 脏读、不可重复读(修改)、幻读(添加/删除)
读已提交(Oracle):读到已经提交的数据。 不可重复读、幻读
可重复读(MySQL): 幻读
序列化读:一个一个读
1、事务的传播特性
就是多个事务方法相互调用时,事务如何在这些方法间传播。
一般情况下我们会把事务设置在Service层,执行Service中的方法过程中对数据库的增删改操作保持在同一个事务中;
如果在Service调用Dao的过程中还调用了Service其他方法,那么其他方法的事
务时如何规定的,必须保证在方法中掉用的其他方法与本身的方法处在同一个事务中,否则如何保证事物的一致性。
2、基于XML的b声明式事务管理
导入Jar包
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
(1)配置事务管理器
常见的事务管理器:JDBC事务管理器、Hibernate事务管理器、JTA分布式事务管理器
<!-- 将SpringJDBC事务管理器添加到Spring容器中 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
(2)配置事务属性
<!--设置事务的属性-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--
name:需要添加方法的方法名称,建议使用通配符表达式*
isolation:数据库的默认隔离级别,默认是default
propagation:事务的传播特性,默认:REQUIRED
read-only:只读
rollback-for:默认情况下对RuntimeException、Error进行回滚,可以通过此属性配
置添加自定义异常的回滚
no-rollback-for:指定哪些异常不回滚
-->
<tx:method name="update*"/>
<tx:method name="add*"/>
<tx:method name="del*"/>
</tx:attributes>
</tx:advice>
(3)配置事务切面
<!--配置事务的切面-->
<aop:config>
<!--切点表达式-->
<aop:pointcut id="transactionPointCut" expression="execution(*com.soft.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="transactionPointCut"/>
</aop:config>
3、基于注解的声明式事务管理
(1)配置事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
(2)开始事务配置的开关,关联事务管理器
<!--开启事务注解的开关-->
<tx:annotation-driven transaction-manager="transactionManager" />
(3)在需要使用事务的类或者方法上添加注解@Transactional
@Transactional写在类上,表示类中所有的方法都使用事务。
写到方法上,表示该方法使用事务。如果类上和方法都存在,采用方法上的事务配置;
@Transactional的属性:参照xml配置
1、导包
dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
2、编写测试类
@RunWith(SpringJUnit4ClassRunner.class)
// classpath不能少,标识的是编译后的文件路径
@ContextConfiguration({"classpath:spring-jdbc.xml"})
public class SpringTestDemo{
@Autowired
QueryController queryController;
@Test
public void test() {
System.out.println(queryController);
}
}
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。
MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,
普通老式 Java 对象)为数据库中的记录。
一款优秀的半自动化的基于ORM思想开发的持久层框架;
ORM(Object Relational Mapping)对象关系映射:
对象和表一一对应
对象中的属性和表字段一一对应
对象的实例和表的一条记录一一对应
1、导包
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
2、编写持久层接口的映射文件(代替接口的实现类)
<?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:MyBtatis3之后是必填项,用于标识这个文件和哪个接口对应;填接口的全限定名称
-->
<mapper namespace="com.soft.dao.StudentDao">
<!--
id:持久层接口的方法名,区分大小写
resultType:
返回值类型,要写类型的全限定名称,由于MyBatis对JDK中一些类型进行了封装,可以直接写类名;
自定义的类型要写全限定名称,配置后也可以写类名
如果返回值是list类型,填List的泛型
-->
<select id="queryCount" resultType="integer">
select count(1) from student
</select>
</mapper>
3、编写MyBatis配置文件,名字没有要求,但是要见名知意,例如: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">
<!--MyBatis的配置文件-->
<configuration>
<!--
加载properties文件
resource:文件的路径,以/分隔开
-->
<properties resource="jdbc.properties"></properties>
<!--
配置数据库的信息
default:默认使用哪个数据库
-->
<environments default="dev">
<!--开发环境-->
<environment id="dev">
<!--事务管理器,使用JDBC的事务管理器-->
<transactionManager type="JDBC"></transactionManager>
<!--数据库链接配置-->
<dataSource type="POOLED">
<property name="driver" value="${mysql.driver}"/>
<property name="url" value="${mysql.url}"/>
<property name="username" value="${mysql.username}"/>
<property name="password" value="${mysql.password}"/>
</dataSource>
</environment>
<!--生产环境-->
<environment id="pro">
<!--事务管理器,使用JDBC的事务管理器-->
<transactionManager type="JDBC"></transactionManager>
<!--数据库链接配置-->
<dataSource type="POOLED">
<property name="driver" value="${mysql.driver}"/>
<property name="url" value="${mysql.url}"/>
<property name="username" value="${mysql.username}"/>
<property name="password" value="${mysql.password}"/>
</dataSource>
</environment>
</environments>
<!--配置映射文件-->
<mappers>
<mapper resource="com/soft/xml/StudentDaoXml.xml"/>
</mappers>
</configuration>
4、编写测试类
public class MyBatisTest {
@Test
public void testQueryForList() throws IOException {
// 配置文件路径
String mybatisXML = "mybatis.xml";
// 配置文件路径创建流对象
InputStream inputStream = Resources.getResourceAsStream(mybatisXML);
// 根据流对象创建Session工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 开启数据库session会话
SqlSession sqlSession = sqlSessionFactory.openSession();
// 创建dao实例
QueryDao mapper = sqlSession.getMapper(QueryDao.class);
System.out.println(mapper.queryCount());
}
}
MyBatis的配置注意事项:
1、持久层接口中方法名不能重复
2、持久层映射xml文件必须添加namespace属性
3、持久层映射xml文件中的标签的id值要和持久层接口中的方法名对应
4、持久层接口中方法的参数最好不要超过1个
5、持久层映射xml文件的所有入参类型都可以省略不写
方案一:按照参数顺序接收参数的值
<select id="queryForObject1" resultType="com.soft.entity.Student">
<!--
多条件查询,SQL接收参数默认不识别参数名,在不指定参数名的情况下,
使用arg0、arg1...来接收参数值;arg0代表第一个参数,arg1代表第二个参数,依此类推
或者
使用param1、param2...来接收参数值;param1代表第一个参数,param2代表第二个参数,以
此类推
-->
select * from student where sno = #{arg0} and age = #{arg1}
select * from student where sno = #{param1} and age = #{param2}
</select>
方案二:指定参数名称
Public Student queryForObject2(@Param("sno") String sno, @Param("age") Integerage);
<select id="queryForObject2" resultType="com.soft.entity.Student">
<!--
多条件查询可以在dao接口,@Param("name")的形式给参数指定名称
-->
select * from student where sno = #{sno} and age = #{age}
</select>
方案三:把参数封装为对象
<select id="queryForObject3" resultType="com.soft.entity.Student">
<!--
入参是对象,SQL接收参数直接写实体类中属性的get方法去掉get之后首字母小写即可
例如:getSno -> sno
-->
select * from student where sno = #{sno} and age = #{age}
</select>
方案四:把参数封装为 Map 对象
<select id="queryForObject4" resultType="com.soft.entity.Student">
<!--
入参是Map,SQL接收参数直接写Map中的key
例如:{sno=100, age=18}
-->
select * from student where sno = #{sno} and age = #{age}
</select>
方案一:不能防止SQL注入
<select id="queryForListMap" resultType="map">
<!--MySQL、Oracle通用写法-->
select * from student where sname likt '%' || ${sname} || '%'
</select>
方案二:可以防止SQL注入
<select id="queryForListMap" resultType="map">
<!--MySQL、Oracle通用写法-->
select * from student where sname like #{sname}
</select>
Student student = new Student();
student.setSname("%张%");
方案三:可以防止SQL注入
<select id="queryForListMap" resultType="map">
<!--MySQL写法-->
select * from student where sname likt concat('%', #{sname}, '%')
</select>
<select id="queryForListMap" resultType="map">
<!--Oracle写法-->
select * from student where sname likt concat('%',concat(#{sname},'%'))
</select>
方案四:可以防止SQL注入
<select id="queryForListMap" resultType="map">
select * from student where sname likt '%' || #{sname} || '%'
</select>
<insert id="add">
<!--
resultType:返回值类型,当前标签中SQL的返回值类型,和入参中指定的类型一致
order:在主SQL执行前执行或者执行后执行;
BEFORE:在主SQL执行前执行
AFTER:在主SQL执行后执行
Oracle生成主键的策略:序列,通过调用序列生成,要优先于主SQL执行,所以要写BEFORE
MySQL生成主键的策略:数据库自增,数据插入之后,数据库自动生成,所以要写AFTER
keyProperty:SQL值要存到哪儿?主要取决于入参的类型,结果会自动封装到入参中
1、入参类型是实体类:值是实体类中对应属性的set方法去掉set之后,首字母小写的内容
2、入参是Map类型:值可以随意,前提是不能和map中已有的key一致
插入数据之后返回主键,常用于注册成功之后返回主键信息,SQL执行成功之后会通过keyProperty把值设置到入参中
-->
<selectKey resultType="String" order="BEFORE" keyProperty="sno" >
select seq_student.nextval sno from dual
</selectKey>
insert into student(sno, sname, sex, age, tel, native_place, class, pass,del)
values (#{sno}, #{sname}, #{sex}, #{age}, #{tel}, #{home}, #{clazz}, #{pass},'1')
</insert>
自定义类型在映射文件中需要写类的全限定名,而Java内置类型则不需要,我们可以给自定义类型起名 字。
<typeAliases>
<!-- 给单个实体类起别名 -->
<typeAlias type="com.soft.entity.Student" alias="student"/>
</typeAliases>
<typeAliases>
<!-- 扫描实体类路径给所有的实体类起别名,名称为类名或者类名首字母小写 -->
<package name="com.soft.entity"/>
</typeAliases>
// 配置文件路径
String mybatisXML = "mybatis-config.xml";
// 配置文件路径创建流对象
InputStream inputStream = Resources.getResourceAsStream(mybatisXML);
// 根据流对象创建Session工厂
SqlSessionFactory sqlSessionFactory = new
SqlSessionFactoryBuilder().build(inputStream);
// 开启session
// true:自动提交事务
// false:不自动提交事务,默认是false
SqlSession sqlSession = sqlSessionFactory.openSession(true);
<!--配置映射文件-->
<mappers>
<!--
name:xml文件所在目录,使用/隔开,因为xml是资源文件,不是java文件。所以找文件值需要
通过目录找,而不是包名
1、xml和dao接口必须在同一个目录下
2、xml的文件名和dao接口的文件名必须一致包含大小写
-->
<package name="com.soft.mapper"/>
</mappers>
@Before
public void loadXML() throws IOException {
// 配置文件路径
String mybatisXML = "mybatis-config.xml";
// 配置文件路径创建流对象
InputStream inputStream = Resources.getResourceAsStream(mybatisXML);
// 根据流对象创建Session工厂
SqlSessionFactory sqlSessionFactory = new
SqlSessionFactoryBuilder().build(inputStream);
// 开启session
sqlSession = sqlSessionFactory.openSession(true);
}
<!--
给映射文件的入参添加jdbcType:org.apache.ibatis.type.JdbcType枚举类
jdbcType的值要全大写
-->
<select id="queryForObject2" resultType="student">
select * from student where sno = #{sno, jdbcType=VARCHAR} and pass = #{pass, jdbcType=VARCHAR}
</select>
Jdbc数据类型 | Java数据类型 | 说明 |
---|---|---|
CHAR | String | 字符串类型 |
VARCHAR | String | 字符串类型 |
NUMBERIC | BigDecimal | 大数据类型 |
BOOLEAN | boolean | 布尔类型 |
INTEGER | int | Integer整形 |
DOUBLE | double | Double双精度类型 |
SMALLINT | short | Short短整形 |
VARBINARY | byte | 二进制数组 |
DATE | java.sql.Date | 日期类型 |
TIME | java.sql.Time | 时间类型 |
TIMESTAMP | java.sql.Timestamp | 时间戳类型 |
<settings>
<setting name="callSettersOnNulls" value="true"/>
</settings>
#{}:可以理解为PreparedStatement,可以预编译,能有效的防止SQL注入。主要操作数据,参数一般为页面输入的值;
${}:可以理解为Statement,不会预编译,不能防止SQL注入。主要操作数据库对象,比如根据不同的字段排序、表、视图等;
常见标签:
代替SQL语句中的where关键字,会自动去掉多余的连接符(and、or)
条件判断,test属性为判断入参的表达式,多个表达式之间使用and、or拼接,表达式不用添加#{}
prefix:SQL拼接的前缀
suffix:SQL拼接的后缀
prefixOverrides:去除sql语句前面的关键字或者字符,该关键字或者字符由prefixOverrides属性指定,假设该属性指定为"AND",当 sql语句的开头为"AND",trim标签将会去除该"AND"
suffixOverrides:去除sql语句后面的关键字或者字符,该关键字或者字符由suffixOverrides属性指定
set>
代替SQL语句中的set关键字,会自动去掉多余的逗号
选择,满足一个条件后其他条件不再判断,可以理解为if-else if-else
遍历,多用于批量执行
collection:遍历的集合类型,list、array、map
item:集合中的对象/泛型引用
index:下标
open:循环以某个字符开头
close:循环以某个字符结尾
separator:循环内容之间的分隔符,会自动去掉多余的分隔符
SQL片段,多用于提取重复的SQL代码
引入SQL片段,refid是标签的id值
<!-- where标签搭配if判断 -->
<select id="queryForListMap" resultType="map">
select * from student
<where>
<if test="sname != null and sname != ''">
and sname = #{sname, jdbcType=VARCHAR}
</if>
<if test="sex != null and sex != ''">
or sex = #{sex, jdbcType=VARCHAR}
</if>
</where>
</select>
<update id="edit">
update student
<set>
<if test="sname != null and sname != ''">
sname = #{sname},
</if>
<if test="age != null and age != ''">
age = #{age},
</if>
<if test="tel != null and tel != ''">
tel = #{tel},
</if>
</set>
where sno = #{sno}
</update>
<select id="queryForListMap" resultType="map">
select * from student
<where>
<choose>
<when test="sname != null and sname != ''">
and sname = #{sname}
</when>
<when test="sex != null and sex != ''">
and sex = #{sex}
</when>
<otherwise>
and sname is not null
</otherwise>
</choose>
</where>
</select>
<delete id="delForList">
delete student where sno in
<foreach collection="list" item="stu" separator="," open="(" close=")">
#{stu.sno, jdbcType=VARCHAR}
</foreach>
</delete>
Oracle的批量插入,使用表的拷贝语句,s_c1, s_c2, s_c3应该是从另一张表中获取。
但是在实际的业务需求中,s_c1, s_c2, s_c3等数据是页面获取的,所以就不存在实际的表。
Oracle 的语法又要求from后面必须跟表名保证SQL的完整性,所以from后面要跟虚拟表dual。
insert into tableName(c1, c2, c3, ...)
(
select s_c1, s_c2, s_c3, ... from subTableName
)
insert into tableName(c1, c2, c3, ...)
(
select 数据1, 数据2, 数据3, ... from dual
union all
select 数据1, 数据2, 数据3, ... from dual
)
Oracle批量插入,带序列
<insert id="addForList">
insert into student(SNO, SNAME, SEX, AGE, TEL, NATIVE_PLACE, CLASS, PASS,DEL)
(
select SEQ_STUDENT.nextval, t.* from
<foreach collection="list" item="stu" separator="union all" open="(" close=")">
select #{stu.sname}, #{stu.sex}, #{stu.age}, #{stu.tel}, #{stu.home}, #{stu.clazz}, #{stu.pass},
'1' from dual
</foreach>
t
)
</insert>
MySQL批量插入
insert into tableName(c1, c2, c3, ...) values
(),
(),
(),
(),
....
<insert id="addForList">
insert into student(SNO, SNAME, SEX, AGE, TEL, NATIVE_PLACE, CLASS, PASS, DEL) values
<foreach collection="list" item="stu" separator=",">
(#{stu.sname}, #{stu.sex}, #{stu.age}, #{stu.tel}, #{stu.home}, #{stu.clazz}, #{stu.pass}, '1')
</foreach>
</insert>
<?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.soft.mapper.QueryMapper">
<sql id="query_sql">sno, sname, sex, age, tel, native_place home, class clazz, pass, head, del</sql>
<select id="queryForList" resultType="student">
select <include refid="query_sql"/> from student
</select>
</mapper>
1、创建全局的映射文件
<?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="global">
<sql id="query_sql">sno, sname, sex, age, tel, native_place home, class clazz, pass, head, del</sql>
</mapper>
2、由于全局的映射文件没有接口映射,所以需要单独配置管理全局的映射文件
<!--配置映射文件-->
<mappers>
<mapper resource="com/soft/mapper/global.xml"/>
<package name="com.soft.mapper"/>
</mappers>
3、引入SQL片段,通过全局映射文件的namespace.sql标签的id
<?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.soft.mapper.QueryMapper">
<sql id="query_sql">sno, sname, sex, age, tel, native_place home, class clazz, pass, head, del</sql>
<select id="queryForList" resultType="student">
select <include refid="global.query_sql"/> from student
</select>
</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.soft.dao.MapperDao">
<!--
id:resultMap的唯一标识
type:完整的Java类或者别名
-->
<resultMap id="rm1" type="student">
<!--
column:需要映射的字段名,以sql查询结果的字段名为准
property:需要把字段映射到实体类对应的属性
jdbcType:数据库中字段对应的类型
javaType:java中属性的类型
-->
<result property="sno" column="sno" javaType="String" jdbcType="INTEGER"/>
<result property="sname" column="sname" javaType="String" jdbcType="VARCHAR"/>
<result property="ssex" column="ssex" javaType="String" jdbcType="VARCHAR"/>
<result property="birthday" column="birthday" javaType="String" jdbcType="DATE"/>
<result property="pass" column="pass" javaType="String" jdbcType="VARCHAR"/>
</resultMap>
<select id="queryForObject1" resultMap="rm1">
select sno, sname, ssex, birthday, pass from student where sno = #{sno, jdbcType=INTEGER}
</select>
</mapper>
实体类
public class Student {
private String sno;
private String sname;
private String ssex;
private String birthday;
private String pass;
private String mno;
private String delFlg;
// 专业的信息
private Major major;
}
映射文件
<?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.soft.dao.MapperDao">
<!--
一个学生对应一个专业
在学生的角度去维护专业信息
要在学生的实体类中去体现专业信息
-->
<resultMap id="rm2" type="student">
<!--学生信息-->
<result property="sno" column="sno" javaType="String" jdbcType="INTEGER"/>
<result property="sname" column="sname" javaType="String" jdbcType="VARCHAR"/>
<result property="ssex" column="ssex" javaType="String" jdbcType="VARCHAR"/>
<result property="birthday" column="birthday" javaType="String" jdbcType="DATE"/>
<result property="pass" column="pass" javaType="String" jdbcType="VARCHAR"/>
<result property="mno" column="mno" javaType="String" dbcType="INTEGER"/>
<result property="delFlg" column="delFlg" javaType="String" jdbcType="INTEGER"/>
<!--专业信息-->
<!--
association:用于描述一对一的关系
property:实体类中的属性
javaType:当前属性对应的Java中的类型(实体类)
-->
<association property="major" javaType="major">
<result property="mno" column="mno" javaType="String" jdbcType="INTEGER"/>
<result property="mname" column="mname" javaType="String" jdbcType="VARCHAR"/>
</association>
</resultMap>
<select id="queryForObject2" resultMap="rm2">
select t1.sno, t1.sname, t1.ssex, t1.birthday, t1.pass, t1.mno,
t1.delFlg, t2.mname
from student t1
left join major t2
on t1.mno = t2.mno
where sno = #{sno, jdbcType=INTEGER}
</select>
</mapper>
实体类
public class Major {
private String mno;
private String mname;
// 很多学生信息
List<Student> students;
}
映射文件
<?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.soft.dao.MapperDao">
<!--
一个专业对应n个学生
在专业的角度去维护学生信息
要在专业的实体类中去体现学生的信息
-->
<resultMap id="rm3" type="major">
<!--专业信息-->
<result property="mno" column="mno" javaType="String" jdbcType="INTEGER"/>
<result property="mname" column="mname" javaType="String" jdbcType="VARCHAR"/>
<!--
学生信息
property:属性名称
ofType:集合依赖的泛型的类型
-->
<collection property="students" ofType="student">
<result property="sno" column="sno" javaType="String" jdbcType="INTEGER"/>
<result property="sname" column="sname" javaType="String" jdbcType="VARCHAR"/>
<result property="ssex" column="ssex" javaType="String" jdbcType="VARCHAR"/>
<result property="birthday" column="birthday" javaType="String" jdbcType="DATE"/>
<result property="pass" column="pass" javaType="String" jdbcType="VARCHAR"/>
<result property="mno" column="mno" javaType="String" jdbcType="INTEGER"/>
<result property="delFlg" column="delFlg" javaType="String" jdbcType="INTEGER"/>
</collection>
</resultMap>
<select id="queryForObject3" resultMap="rm3">
select t1.mno, t1.mname, t2.sno, t2.sname, t2.ssex, t2.birthday, t2.pass,
t2.delFlg
from major t1
left join student t2
on t1.mno = t2.mno
where t1.mno = 1
</select>
</mapper>
/*
存储过程没有返回值,所以接口的方法也不需要返回值
存储过程的输出结果会设置到入参中
*/
public void queryForPro(Student stu);
<!--
useCache:不使用缓存
statementType:执行器类型为CALLABLE
mode:参数的类型,in表示输入参数,out表示输出参数,inout表示输入输出参数
out类型的参数必须添加jdbcType
-->
<select id="queryForPro" useCache="false" statementType="CALLABLE">
{
call queryById(
#{sno, mode=IN, jdbcType=VARCHAR},
#{sname, mode=OUT, jdbcType=VARCHAR},
#{age, mode=OUT, jdbcType=VARCHAR}
)
}
</select>
1、导包
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2、log4j日志的组成部分
1、日志级别
由高到低:DEBUG -> INFO -> WARN -> ERROR
级别越高打印的内容越多
2、日志的输出目的地(Appender)
控制台:org.apache.log4j.ConsoleAppender
文件:org.apache.log4j.RollingFileAppender、
org.apache.log4j.DailyRollingFileAppender
邮件:org.apache.log4j.net.SMTPAppender
数据库:org.apache.log4j.jdbc.JDBCAppender
3、日志的布局(layout)
%p:日志级别
%c:类名
%m:信息
%n:换行
%d:日期{yyyy-mm-dd hh:mm:ss}
# 日志的级别,日志目的地
# 本文中设定的是全局的DEGUB,为的是开发过程中方便查看调试信息
# 日志输入目的地设置了4个,分别是控制台,文件,邮件,数据库
log4j.rootLogger=DEBUG,console,logFile,mail,jdbc
#################################控制台打印#################################
# 配置目的地
# Console console = new ConsoleAppender();
log4j.appender.console=org.apache.log4j.ConsoleAppender
# 日志打印的类别,err:错误信息,字体颜色为红色;out:打印信息,
log4j.appender.console.target=System.err
# 配置控制台信息的布局
log4j.appender.console.layout=org.apache.log4j.PatternLayout
#%p:日志级别
#%c:类名
#%m:信息
#%n:换行
#%d:日期{yyyy-MM-dd hh:mm:ss}
log4j.appender.console.layout.ConversionPattern=[%p] %d{yyyy-MM-dd hh:mm:ss} %c :
%m%n
#################################输出到文件#################################
# log4j.appender.file=org.apache.log4j.RollingFileAppender
# log4j.appender.file.MaxFileSize=10240
# file表示文件路径
# 可以是相对路径也可以是绝对路径
# log4j.appender.file.File=myLog.log
# 配置时间
# log4j.appender.logFile.datePattern=yyyy-MM-dd
# -----------------------------------------------------------------------
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.File=myLog.log
# 配置时间
log4j.appender.logFile.datePattern=yyyy-MM-dd
#################################输出到邮件#################################
# java发送邮件需要导jar包:javax.mail
log4j.appender.mail=org.apache.log4j.net.SMTPAppender
log4j.appender.mail.SMTPUsername=发件人登录邮箱的账号,一般为邮箱
log4j.appender.mail.SMTPPassword=发件人登录邮箱的密码
log4j.appender.mail.From=发件人邮箱
log4j.appender.mail.SMTPHost=发件人邮箱服务器,smtp.163.com
log4j.appender.mail.SMTPPort=发件人邮箱服务器端口,25
log4j.appender.mail.Subject=邮件主题
log4j.appender.mail.To=收件人邮箱
# 配置邮件的布局,邮箱使用HTML布局
log4j.appender.mail.layout=org.apache.log4j.HTMLLayout
#################################输出到数据库#################################
log4j.appender.jdbc=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.jdbc.Driver=oracle.jdbc.driver.OracleDriver
log4j.appender.jdbc.URL=jdbc:oracle:thin:@localhost:1521:orcl
log4j.appender.jdbc.user=root
log4j.appender.jdbc.password=root
log4j.appender.jdbc.sql=insert into log values(seq_log.nextval, '%c', '%m',
sysdate)
# 自定义当前appender的日志级别
log4j.appender.jdbc.threshold=INFO
# 配置数据库的信息布局
log4j.appender.jdbc.layout=org.apache.log4j.PatternLayout
# 单独针对某个包设置打印级别
log4j.logger.com.soft.mapper=DEBUG
3、MyBatis配置文件添加log4j配置
<settings>
<!--固定写法-->
<setting name="logImpl" value="log4j"/>
</settings>
MyBatis的一级缓存默认是开启,不需要手动配置。MyBatis的一级缓存存在于SqlSession的生命周 期中,在同一个SqlSession中查询时,MyBatis会把执行的方法和参数通过算法生成缓存的键值, 将键值和查询结果存入一个Map对象中。如果同一个SqlSession中执行的方法和参数完全一致,那 么通过算法会生成相同的键值,当Map缓存对象中己经存在该键值时,则会返回缓存中的对象。也 就是说MyBatis判断调用的方法和参数不变时会从缓存中取值。
如果在调用完查询操作之后,把数据库中的数据修改;再次调用查询操作,参数不变。那么,只会 调用一次数据库,此时的结果就跟数据库的结果不对应。如何解决这个问题呢?可以在xml映射文 件的查询操作添加【 flushCache=“true” 】即可。
public class CacheTest1 {
@Test
public void cache() throws IOException {
// 获取配置文件的输入流
InputStream inputStream = Resources.getResourceAsStream("mybatisconfig.xml");
// 通过SQL的会话工厂的构建类,构建一个SQL的会话工厂
SqlSessionFactory sqlSessionFactory = new
SqlSessionFactoryBuilder().build(inputStream);
// 开启SQL会话,相当于获取了数据库的链接
SqlSession sqlSession1 = sqlSessionFactory.openSession();
// 获取实例
CacheDao mapper1 = sqlSession1.getMapper(CacheDao.class);
/* 同一个方法,相同参数调用,第一次会调用数据库获取结果;第二次从缓存中共获取结果 */
Map<String, Object> map1 = mapper1.queryForMap("4");
Map<String, Object> map2 = mapper1.queryForMap("4");
System.out.println("-----------------------------------------------");
/* 再次开启Session,调用同一个方法,传递相同参数会执行数据库获取结果; */
SqlSession sqlSession2 = sqlSessionFactory.openSession();
// 获取实例
CacheDao mapper2 = sqlSession2.getMapper(CacheDao.class);
Map<String, Object> map3 = mapper2.queryForMap("4");
}
}
MyBatis 一级缓存最大的共享范围就是一个SqlSession内部,那么如果多个 SqlSession 需要共享缓 存,则需要使用二级缓存。当二级缓存开启后,同一个命名空间(namespace) 所有的操作语句,都 影响着一个共同的 cache,也就是二级缓存被多个 SqlSession 共享,是一个全局的变量。当开启 缓存后,数据的查询执行的流程就是 二级缓存 => 一级缓存 => 数据库。
实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。开启二级缓存的条件也是比较 简单,通过直接在 MyBatis 配置文件配置,还需要在映射文件中添加标签。
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
public class CacheTest1 {
@Test
public void cache() throws IOException {
// 获取配置文件的输入流
InputStream inputStream = Resources.getResourceAsStream("mybatisconfig.xml");
// 通过SQL的会话工厂的构建类,构建一个SQL的会话工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 开启SQL会话,相当于获取了数据库的链接
SqlSession sqlSession1 = sqlSessionFactory.openSession();
// 获取实例
CacheDao mapper1 = sqlSession1.getMapper(CacheDao.class);
Map<String, Object> map1 = mapper1.queryForMap("4");
Map<String, Object> map2 = mapper1.queryForMap("4");
/* 同一个方法,相同参数调用,第一次会调用数据库获取结果;第二次从缓存中共获取结果; */
System.out.println("map1 == map2 : " + (map1 == map2));
sqlSession1.close();
System.out.println("-----------------------------------------------");
/* 再次开启Session,调用同一个方法,传递相同参数从缓存中共获取结果; */
SqlSession sqlSession2 = sqlSessionFactory.openSession();
// 获取实例
CacheDao mapper2 = sqlSession2.getMapper(CacheDao.class);
Map<String, Object> map3 = mapper2.queryForMap("4");
sqlSession1.close();
}
}
映射语句文件中的所有SELECT语句将会被缓存。
映射语句文件中的所有时INSERT、UPDATE、DELETE语句会刷新缓存。
缓存会使用LeastRecentlyUsed(LRU,最近最少使用的)算法来收回。
根据时间表(如noFlushInterval,没有刷新间隔),缓存不会以任何时间顺序来刷新。
缓存会存储集合或对象(无论查询方法返回什么类型的值)的1024个引用。
缓存会被视为read/write(可读/可写)的,意味着对象检索不是共享的,而且可以安全地被调用者修改,
而不干扰其他调用者或线程所做的潜在修改。
缓存的执行流程:
当用户发送一个查询请求:
判断一级缓存是否存在:
存在:从缓存中读取
不存在:
判断二级缓存是否开启
如果二级缓存开启
判断二级缓存中是否存在数据
存在:从缓存中读取
不存在:发送查询语句到数据库查询。
如果二级缓存没有开启
发送查询语句到数据库查询。
一级缓存什么时候可以当作同一个查询:
一个SqlSeession可以看作一个查询;
一旦当前有增删改操作时,针对同一个mybatis会默认当作两个查询。
官网:https://pagehelper.github.io/
1、导包
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelperartifactId>
<version>5.1.11version>
dependency>
2、配置分页插件
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"/>
plugins>
3、编写测试类
public class PageTest {
Logger logger = Logger.getLogger(PageTest.class);
static SqlSession sqlSession;
@BeforeClass
public static void loadXML() throws IOException {
// 获取配置文件的输入流
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
// 使用工厂的创建类,根据配置文件的流生成SqlSessionFactory
SqlSessionFactory ssf = new
SqlSessionFactoryBuilder().build(inputStream);
// 打开session
sqlSession = ssf.openSession(true);
}
@Test
public void queryTest() {
PageDao pageDao = sqlSession.getMapper(PageDao.class);
/*
配置数据的开始位置和每页显示条数
一定要紧紧的挨着查询操作的代码
*/
// 每页显示条数
int limit = 5;
// 第几页
int pageNum = 1;
int offset = (pageNum - 1) * limit;
PageHelper.offsetPage(offset, limit);
List<Map<String, String>> query = pageDao.query();
PageInfo pageInfo = new PageInfo(query);
System.out.println(pageInfo);
// 获取分页后的数据
List list = pageInfo.getList();
list.forEach(map -> {
System.out.println(map);
});
}
}
Spring和MyBatis整合关键在于决定原来MyBatis管理的文件交给谁管理?
1、由于Spring管理起来更为合理,所以把数据源的管理和接口的映射文件交给spring管理。
2、MyBatis的核心是SqlSessionFactory
Spring:容器
1、管理Controller、Service、Dao(Mapper)对象的注入
2、管理数据库相关(数据库链接、数据库连接池、事务)
3、切面管理
MyBatis:持久层框架
1、管理数据库相关(数据库链接、数据库连接池、事务) ==> 交给Spring管理
2、管理持久层接口和映射文件 ==> 自己管理 ==> 通过Spring代管理用的还MyBatis核心
3、缓存、日志、分页插件 ==> 自己管理 ==> 通过Spring代管理用的还MyBatis核心
1、导包
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.2.9.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-txartifactId>
<version>5.2.9.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>5.2.9.RELEASEversion>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.3version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>2.0.3version>
dependency>
2、Spring持久层的配置文件中配置MyBatis,同时可以删除持久层接口对应的实体类,MyBatis是不需要 实体类的; 如果有需要也可以编写mybatis的配置文件,比如实体类起别名等。
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>
<settings>
<setting name="callSettersOnNulls" value="true"/>
<setting name="logImpl" value="log4j"/>
</settings>
<!--给实体类起别名,方便映射文件设置对象类型-->
<typeAliases>
<!--
扫描实体类所在的包,别名叫做类名或者类名的首字母小写
Student student
-->
<package name="com.soft.entity"/>
</typeAliases>
</configuration>
spring-dao.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 驱动、url、账号、密码 -->
<!--加载properties文件-->
<context:property-placeholder location="jdbc.properties"/>
<!-- 数据库连接池 -->
<!--数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.uname}"/>
<property name="password" value="${jdbc.pwd}"/>
</bean>
<!--引入jdbc的事务管理器-->
<!-- 可以理解为增强类 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启事务注解的开关-->
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- 配置mybatis -->
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--加载mybatis配置文件-->
<property name="configLocation" value="classpath:mybatis.xml"/>
<!--加载数据源-->
<property name="dataSource" ref="dataSource"/>
<!--扫描映射文件所在包-->
<property name="mapperLocations" value="classpath:com/soft/dao/*.xml"/>
<!--实体类起别名-->
<property name="typeAliasesPackage" value="com.soft.entity"/>
<property name="configurationProperties">
<props>
<!--map映射为空配置-->
<prop key="callSettersOnNulls">true</prop>
</props>
</property>
</bean>
<!--扫描接口的包-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--扫描接口和映射文件最对应-->
<property name="basePackage" value="com.soft.dao"/>
<!--和sessionFactory关联-->
<property name="sqlSessionFactoryBeanName" value="sessionFactory"/>
</bean>
</beans>
M(Model):模型(业务模型、数据模型)
V(View):视图(jsp、html、pdf、excel…)
C(Controller):控制(接收和响应)
SpringMVC组件:
1、前端控制器:DispatcherServlet(不用自己写)
2、处理器映射器:HandlerMapping(不用自己写)
3、处理器适配器:HandlerAdapter(不用自己写)
4、处理器:Handler(自己写)
5、视图解析器:ViewResolver(不用自己写)
1、用户发送请求到前端控制器(DispatcherServlet),把请求交给处理器映射器(HandlerMapping)查找要执行的处理器
2、处理器映射器(HandlerMapping)返回执行器链(HandlerMappingExecutionChain)给前端控制器(DispatcherServlet)
3、前端控制器根据返回的执行器链给处理器适配器(HandlerAdapter)
4、处理器适配器(HandlerAdapter)请求执行处理器(Handler),返回ModelAndView给处理器适配器
5、处理器适配器(HandlerAdapter)把ModelAndView返回给前端控制器(DispatcherServlet)
6、前端控制器(DispatcherServlet)把ModelAndView交给视图解析器(ViewResolver)去解析,把逻辑视图解析为物理视图
7、视图解析器(ViewResolver)把解析好的物理视图和数据,返回给前端控制器(DispatcherServlet)
8、前端控制器(DispatcherServlet)把物理视图交给浏览器渲染
由于是web项目,要求在服务器启动的时候加载springMVC的核心,同时加载MVC的其他组件; SpringMVC是一个Servlet的衍生产物,所以在web.xml中通过servlet标签加载DispatcherServlet。
导包
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.3.1version>
dependency>
自动依赖Spring其他的核心jar包
spring-mvc.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<bean name="/sc" class="com.soft.controller.StudentController"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"/>
beans>
web.xml
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>SpringMVCservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath*:spring-mvc.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>SpringMVCservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
web-app>
控制器
// 实现Controller接口目的是为了让处理器适配识别
public class StudentController implements Controller {
/**
* @param httpServletRequest 请求
* @param httpServletResponse 响应
* @return ModelAndView 模型(数据模型)和视图(逻辑视图)
* @throws Exception
*/
@Override
public ModelAndView handleRequest(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) throws Exception {
// 模型视图对象
ModelAndView mav = new ModelAndView();
// 添加数据模型 req.setAttribute();
mav.addObject("msg", "Hello SpringMVC");
// 设置视图路径
mav.setViewName("success.jsp");
return mav;
}
}
通过翻看底层源码DispatcherServlet.properties,发现SpringMVC已经帮我们配置了默认组件,所以也 简化了开发流程。工作中最常用的是注解的开发形式。
注解介绍
@Controller:
标识类是一个处理器
当前类交给Spring管理
@RequestMapping:请求的路径,可以写到类上和方法上
value/path:请求的路径
method:标识方法能接收什么样的请求方式,可以从枚举类RequestMethod中选择,
get/post/delete/put...
param:要求请求的参数必须携带指定参数,必要时还可以指定参数的名称
开发流程
控制器方法编写:
1、页面需不需要传参:决定方法的参数以及类型个数等
2、需不需要返回数据:决定方法的返回值类型
1)只返回页面:返回值String,此时不建议添加@RestController和@ResponseBody两个注解
2)有页面和数据:返回值ModelAndView
编写SpringMVC配置文件,由于是注解开发,只需要开启注解的开关即可
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.soft.controller"/>
beans>
web.xml中配置SpringMVC的核心控制器以及加载SpringMVC的配置文件
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>SpringMVCservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath*:spring-mvc4.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>SpringMVCservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
web-app>
1、from表单参数绑定
from表单的name属性的值和控制层方法的参数名称一致;
代码已经完成,不便修改情况下,可以通过注解@RequestParam(“名称”)的方式和fom表单一致。
/*
SpringMVC自带类型转化器,可以将页面传递的参数转换成指定类型
*/
@RequestMapping("/form_param")
public ModelAndView getParam(@RequestParam("user") String name, @RequestParam("pass") String pass,
Integer sex, String[] hobby, String option, Integer number) {
System.out.println(name);
System.out.println(pass);
System.out.println(sex);
System.out.println(hobby);
System.out.println(Arrays.toString(hobby));
System.out.println(option);
System.out.println(number);
return null;
}
2、URL参数绑定
URL路径(超链接)参数的key值和控制层方法的参数名称一致;
代码已经完成,不便修改情况下,可以通过注解@RequestParam(“名称”)的方式和fom表单一致。
URL参数绑定
@RequestMapping("/url_param")
public ModelAndView getParam(@RequestParam("user") String name, String pass) {
System.out.println(name + pass);
return null;
}
form表单传值使用场景:数据量较大时
登录、注册、提交修改的数据、添加
URL传值使用场景:数据量较小时
删除、查询修改的数据
3、对象参数绑定
from表单的name属性的值(URL参数中key值)和实体类中属性一致(set方法去掉set之后内容一 致)
@RequestMapping("/obj_param")
public ModelAndView getParam(Student stu) {
System.out.println(stu);
return null;
}
4、Map映射
参数较多的情况下除了使用实体类,Map映射也是一个不错的选择。不管是表单提交还是URL传 值,都可以将参数映射到map中;
表单传值:Map的key是name属性的值;复选框传值不建议使用Map映射,相同的name(key)会 覆盖;
URL传值:Map的key是参数的key;
@RequestMapping("/map_param")
public ModelAndView getParam(@RequestParam Map<String, String> map) {
System.out.println(map);
return null;
}
5、URL占位符
// {flg}占位符的名字和参数名一致
@RequestMapping("/url/{flg}")
// 参数前添加注解@PathVariable
public ModelAndView getParam(Student stu, @PathVariable String flg) {
System.out.println(flg);
System.out.println(stu);
return null;
}
6、ServletAPI
@RequestMapping("/getParamByServletAPI")
public ModelAndView getParamByServletAPI(HttpServletRequest req, HttpServletResponse resp) {
System.out.println("getParamByServletAPI......");
String sno = req.getParameter("sno");
System.out.println(sno);
System.out.println(req.getSession());
return null;
}
7、响应JSON数据
导包
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-coreartifactId>
<version>2.9.9version>
dependency>
<dependency>
<groupId>com.jwebmp.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>0.63.0.19version>
dependency>
编写控制类
/*
SpringMV对JSON数据转换的处理已经非常完善了
不管是什么类型的数据都可以以JSON类型返回给浏览器
所以只需要在返回值的前面添加注解 @ResponseBody 即可,或者在类上添加@RestController
@ResponseBody:把当前方法的返回值封装为JSON数据返回
@RestController:当前类中所有的方法都以JSON数据返回
*/
@RequestMapping("/jsonString")
@ResponseBody
public String sendJsonString(){
return "字符串类型的JSON";
}
打开注解开关
JSON响应中文乱码问题:
方案一: produces属性
@RequestMapping(value = "/str", method = {RequestMethod.GET}, produces = {"text/html;charset=utf-8"})
@ResponseBody
public String strEncoding(){
return "中文不乱码";
}
方案二:SpringMVC全局配置
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8value>
<value>application/json;charset=UTF-8value>
<value>text/plain;charset=UTF-8value>
<value>application/xml;charset=UTF-8value>
list>
property>
bean>
mvc:message-converters>
mvc:annotation-driven>
方案三:Response设置响应头信息
@RequestMapping(value = "/str", method = {RequestMethod.GET})
public void strEncoding(HttpServletResponse response) throws IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.print("中文不乱码");
out.flush();
out.close();
}
8、接收JSON数据
@RequestBody主要用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的);
GET方 式无请求体,所以使用@RequestBody接收数据时,前端不能使用GET方式提交数据,而是用POST 方式进行提交。
@RequestBody注解常用来处理content-type不是默认的application/x-www-form-urlcoded编码的内 容(form表单和URL传值形式)。一般情况下来说常用其来处理application/json类型。(jquery异步请 求默认是application/x-www-form-urlcoded)
导包
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-coreartifactId>
<version>2.9.9version>
dependency>
<dependency>
<groupId>com.jwebmp.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>0.63.0.19version>
dependency>
使用String接收JSON数据
@RequestMapping(path="/sendJson1")
@ResponseBody
public String getJson1(@RequestBody String jsonStr){
System.out.println(jsonStr);
return jsonStr;
}
使用对象接收JSON数据
@RequestMapping(path="/sendJson2")
@ResponseBody
public Student getJson2(@RequestBody Student student){
System.out.println(student);
return student;
}
控制器接收页面JSON数据(JSON格式的数据)
控制器方法的参数添加:@RequestBody
异步请求添加:contentType: "application/json;charset=utf-8"
9、自定义类型转换器
SpringMVC底层已经封装了很多的类型转换器,也就是为什么我们页面上传的字符串可以使用Integer接 收的原因。那么并不是所有类型的字符都可以正常转换,比如日期字符“2020/01/01”可以正常转换,而 “2020-01-01”就不能转换。怎么来决解这个问题,就需要自定义类型转换器来解决。
通过调查源码得知,内置的转换器都实现了Converter接口,所以要想让SpringMVC识别自定义的 转换器,也需要实现这个接口。
package com.soft.converter;
import org.springframework.core.convert.converter.Converter;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 字符类型转换日期类型
**/
public class StringToDateConverter implements Converter<String, Date> {
@Override
public Date convert(String source) {
if(source == null || "".equals(source.trim())){
throw new RuntimeException("value not null !");
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
try {
return sdf.parse(source);
} catch (Exception e) {
throw new RuntimeException("Date Format Exception");
}
}
}
SpringMVC是基于组件开发的一个框架,每个框架要想使用必须要先注册,所以自定义类之后要注册到 SpringMVC服务中。
转发(forward):
请求地址不发生改变
可以携带数据
只能请求本项目中的路径
重定向(redirect):
请求地址发生改变
不可以携带数据
可以请求本项目和以外的路径、
SpringMVC默认的响应方式是转发。
SpringMVC的重定向:
在返回的请求路径前添加【redirect:】即可,比如:redirect:success.jsp
重定向不经过核心控制器
重定向的路径不推荐写视图路径,WEB-INF目录之外的视图除外
文件上传目的地有:当前项目所在目录、远程服务器、数据库; 项目当前所在目录和远程服务器是使用相对较多的两种方式。
项目当前所在目录:会把文件上传到当前项目所在目录中,同时在数据库中存储文件路径。 远程服务器:目前主流形式是分布式文件存储服务器(MongoDB); 数据库:虽然有字节类型可以存储文件、图片等资源,但是非常不推荐使用此种方式。
要求: 1、要求form表单的请求方式必须是POST
2、要求form表单添加属性,enctype=“multipary/form-data”
3、配置文件上传的解析器,id属性的值必须是”multipartResolver“
4、处理器的参数要求添加参数“MultipartFile file",同时要和form表单的name属性值一致
导包:
<dependency>
<groupId>commons-fileuploadgroupId>
<artifactId>commons-fileuploadartifactId>
<version>1.4version>
dependency>
文件上传解析器
<!--文件上传的解析器-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--编码格式-->
<property name="defaultEncoding" value="utf-8"/>
<!--文件上传的大小限制,以字节的形式配置,-1表示没有限制-->
<property name="maxUploadSize" value="-1"/>
</bean>
控制器
@RequestMapping("/upload3")
public String upload3(@RequestParam("img") MultipartFile file, HttpServletRequest req) throws Exception {
// 先拿到文件信息
// 文件名
String fileName = file.getOriginalFilename();
fileName = UUID.randomUUID().toString().replace("-", "") + "-" + fileName;
// 服务器路径
String path = req.getServletContext().getRealPath("/img/");
File serverFile = new File(path);
// 判断文件是否存在,不存在就创建文件夹
if(!serverFile.exists()){
serverFile.mkdirs();
}
path += fileName;
// 把文件写入到磁盘
file.transferTo(new File(path));
return "success";
}
导包
<dependency>
<groupId>com.sun.jerseygroupId>
<artifactId>jersey-clientartifactId>
<version>1.19.4version>
dependency>
<dependency>
<groupId>com.sun.jerseygroupId>
<artifactId>jersey-coreartifactId>
<version>1.19.4version>
dependency>
控制器
@RequestMapping("/upload4")
public String upload4(@RequestParam("img") MultipartFile file) throws Exception {
// 获取文件名
String originalFilename = file.getOriginalFilename();
// 改造文件名
originalFilename = UUID.randomUUID().toString().replace("-", "") + "-" +
originalFilename;
// 远程服务器(直接创建一个web工程)
String fileServerPath = "http://localhost:9090/FileServer/img/" +
originalFilename;
// 客户端
Client client = Client.create();
// 通过客户端获取的连接
WebResource resource = client.resource(fileServerPath);
resource.put(file.getBytes());
return "success";
}
跨域上传文件需要配置tomcat安装目录下的web.xml
<init-param>
<param-name>readonlyparam-name>
<param-value>falseparam-value>
init-param>
把文件内容响应到浏览器的过程
返回ResponseEntity对象
@RequestMapping("/download1")
public ResponseEntity<byte[]> download1(String file, HttpServletRequest request) throws IOException {
// 服务路径
String serverPath = request.getServletContext().getRealPath("/upload/");
// 文件的完整路径
String path = serverPath + file;
// 把文件读取到字节数组中
FileInputStream fis = new FileInputStream(new File(path));
byte[] bytes = new byte[fis.available()];
fis.read(bytes);
// 设置下载的响应头信息
HttpHeaders hh = new HttpHeaders();
hh.setContentDispositionFormData("attachement", "sm.jpg");
return new ResponseEntity<byte[]>(bytes, hh, HttpStatus.OK);
}
通过response响应
@RequestMapping("/download2")
public void download2(String file, HttpServletRequest request, HttpServletResponse resp) throws IOException {
// 服务路径
String serverPath = request.getServletContext().getRealPath("/upload/");
// 文件的完整路径
String path = serverPath + file;
// 把文件读取到字节数组中
FileInputStream fis = new FileInputStream(new File(path));
byte[] bytes = new byte[fis.available()];
fis.read(bytes);
// 设置下载的响应头信息
resp.setHeader("content-disposition","attachement;filename=sm.jpg");
ServletOutputStream outputStream = resp.getOutputStream();
outputStream.write(bytes);
outputStream.flush();
outputStream.close();
}
在业务流程的处理中经常会发生异常,如果这些异常不进行处理,会直接展示在页面中,对用户来说是 不有好的。为了在出现异常使给用户展示友好的页面,需要自定义处理异常。
1、自定义异常类,用于自定义异常
package com.soft.exception;
public class SysException extends Exception {
private String message;
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public SysException(String message) {
this.message = message;
}
}
2、自定义异常处理器,用于在发生异常时候给用户一个友好的界面。
要求:实现HandlerExceptionResolver接口,重写resolveException方法
public class SysExceptionResolver implements HandlerExceptionResolver {
/**
* 异常处理
* @param request Http请求
* @param response Http响应
* @param handler 核心代码抛出异常所在的方法
* @param ex 核心代码抛出的异常
* @return
*/
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
SysException se = null;
// 判断抛出的异常是否是自定义异常
if(ex instanceof SysException){
// 把异常强转为自定义异常
se = (SysException) ex;
} else {
// 创建自定义异常
se = new SysException("系统维护中...");
}
ModelAndView mav = new ModelAndView();
// 异常错误信息展示页面
mav.setViewName("/page/500");
// 异常错误信息
mav.addObject("errMsg", se.getMessage());
return mav;
}
}
3、把异常处理器组件添加到SpringMVC中,在SpringMVC配置文件中直接使用bean标签配置即可
<bean class="com.soft.exception.SysExceptionResolver"/>
1、过滤器:Filter;拦截器:Interceptor
2、过滤器是Servlet的一个标准组件,任何的web项目都可以使用;拦截器是SpringMVC的组件,只能在
SpringMVC中使用
3、拦截器只拦截控制器的请求
1、编写自定义拦截器类,要求该类继承HandlerInterceptorAdapter类或者实现HandlerInterceptor接 口,重写相应方法
// 在控制器方法执行之前执行
// 返回true:放行;返回false:不放行
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler);
// 在控制器方法执行之后,页面加载之前执行
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView);
// 页面加载之后执行
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
2、在springMVC配置文件中添加拦截器组件
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/user/*"/>
<mvc:exclude-mapping path=""/>
<bean class="com.soft.interceptor.MyInterceptor"/>
mvc:interceptor>
mvc:interceptors>
<mvc:interceptors>
<bean class="com.soft.interceptor.MyInterceptor"/>
mvc:interceptors>
解决方案:
1、form表单提交,在提交之后把提交按钮禁用,防止重复提交
2、在请求的路径上添加一个标识(令牌)每次校验标识
令牌注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Token {
TokenType value();
}
注解值:通过枚举类固定值的范围
public enum TokenType {
CREATE, VALIDATE
}
登录相关操作
@Controller
@RequestMapping("/login")
public class LoginController {
// 去登录页:去动物园,要买票
@RequestMapping(value = "/toLogin")
@Token(TokenType.CREATE)
public String toLogin(){
return "/login.jsp";
}
// 登录:过闸机验票
@RequestMapping("/validate")
@Token(TokenType.VALIDATE)
public String login(String token){
return "/success.jsp";}
}
}
public class TokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 处理器的方法对象
HandlerMethod handlerMethod = null;
HttpSession session = request.getSession();
if(handler != null) {
handlerMethod = (HandlerMethod) handler;
// 获取指定方法上的注解
Token annotation = handlerMethod.getMethodAnnotation(Token.class);
// 创建令牌
if (annotation.value().equals(TokenType.CREATE)) {
// 创建令牌UUID、随机数
String uuid = UUID.randomUUID().toString();
// 生成的令牌要存起来,方便验证时验证
session.setAttribute("token", uuid);
// 直接放行,去到指定的页面
return true;
} else if (annotation.value().equals(TokenType.VALIDATE)) {
// 获取页面提交的令牌:逛动物园之前购买的票
String token = request.getParameter("token");
// 获取上次一请求的路径
String referer = request.getHeader("Referer");
// 是否验证通过标识,ture表示通过,false表示不通过。
boolean flg = false;
// 页面提交的令牌是空的
if (token == null || "".equals(token)) {
// 重新生成令牌
flg = false;
} else {
// 获取服务器生成的令牌
Object obj = session.getAttribute("token");
// 服务器的令牌为空
if (obj == null) {
// 重新生成令牌
flg = false;
} else {
String sessionToken = (String) obj;
// 验证令牌
if (token.equals(sessionToken)) {
// 验证成功,销毁票根
request.getSession().removeAttribute("token");
flg = true;
return true;
} else {
// 重新生成令牌
flg = false;
}
}
}
if(!flg){
session.setAttribute("msg", "登录超时请重新登录!!");
response.sendRedirect(referer);
}
}
}
return false;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
request.getSession().removeAttribute("msg");
}
}
zh_CN、ja_JP、en_US
1、编写国际化资源文件
文件名:前缀_语言标识.properties,properties文件中的所有key值保持一致
message_en_US.properties
user=Account
head_img=Photo
message_ja_JP.properties
user=\u30a2\u30b3\u30f3\u30c8
head_img=\u5199\u771f
message_zh_CN.properties
user=\u8d26\u6237
head_img=\u5934\u50cf
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="defaultEncoding" value="utf-8"/>
<property name="basename" value="message"/>
bean>
3、设定国际化的实现方式
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"/>
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>
4、配置国际化拦截器
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="lang"/>
bean>
mvc:interceptors>
5、静态页面,导入spring标签,使用读取properties文件的key
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head>
<title>Titletitle>
<link type="text/css" rel="stylesheet" href="${pageContext.request.contextPath}/css/index.css">
head>
<body>
<h2>Hello World!h2>
<a href="?lang=zh_CN">中文a>
<a href="?lang=ja_JP">日文a>
<a href="?lang=en_US">英文a>
<form action="${pageContext.request.contextPath}/stu/upload" method="post" enctype="multipart/form-data">
<spring:message code="user" />:<input type="text" name="user">
<spring:message code="head_img" />:<input type="file" name="img"/>
<input type="submit" value="提交">
form>
body>
html>
注意:不能直接请求jsp页面,需要通过后台转发到jsp页面session才能起作用。
应用场景:页面语言、提示信息获取
静态资源:js、css、jpg、mp3、mp4等
由于在配置SpringMVC时,拦截的路径是/,覆盖了tomcat中加载静态资源的路径,所以需要额外 配置静态资源加载。如果配置SpringMVC时拦截的路径是*.后缀的方式,则不用考虑静态资源问 题。
静态资源要放到/WEB-INF/同一级目录下
方案一:在SpringMVC配置文件中配置静态资源
方案二:在SpringMVC配置文件中配置静态资源路径和映射
<mvc:annotation-driven/>
<!--
mapping:页面请求资源的路径,可以使用通配符
location:项目中资源文件的实际目录
-->
<mvc:resources mapping="/img/*" location="/img/"/>
<mvc:resources mapping="/css/*" location="/css/"/>
方案三:在web.xml中配置静态资源
<servlet-mapping>
<servlet-name>defaultservlet-name>
<url-pattern>*.pngurl-pattern>
<url-pattern>*.jsurl-pattern>
<url-pattern>*.cssurl-pattern>
<url-pattern>*.gifurl-pattern>
<url-pattern>*.jpgurl-pattern>
servlet-mapping>
Restful(Representational State Transfer:表现层状态转化)一种软件架构风格、设计风格,而不 是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这 个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。 Restful是一种互联网应用程序的API设计理念:URL定位资源,用HTTP动词 (GET,POST,DELETE,DETC)描述操作。
传统方式操作资源:
查询(get):http://localhost:8080/users/queryUser?id=xxx
添加(post):http://localhost:8080/users/addUser
删除(post):http://localhost:8080/users/delUser?id=xxx
修改(get/post):http://localhost:8080/users/editUser
Restful风格操作资源:
查询列表(get):http://localhost:8080/users/
查询某个用户(get):http://localhost:8080/users/1001
添加用户信息(post): http://localhost:8080/users/
修改用户全部信息(put): http://localhost:8080/users/
修改用户部分信息(patch): http://localhost:8080/users/
删除用户信息(delete): http://localhost:8080/users/1001
传统的操作从功能角度看是没有问题的,但也存在一些问题。每次请求的接口或者地址,都在做描 述,例如查询的时候用了queryUser,新增的时候用了addUser,修改的时候用了editUser,其实完 全没有这个必要。使用了get请求,就是查询,使用post请求,就是新增的请求,PUT就是修改, delete就是删除,我的意图很明显,完全没有必要做描述,这就是为什么有了RestFul。
通过 GET、 POST、 PUT、 PATCH、 DELETE 等方式对服务端的资源进行操作。其中,GET 用于 查询资源,POST 用于创建资源,PUT 用于更新服务端的资源的全部信息,PATCH 用于更新服务端 的资源的部分信息,DELETE 用于删除服务端的资源。
导包
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poiartifactId>
<version>4.1.1version>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poi-ooxmlartifactId>
<version>4.1.1version>
dependency>
Excel导入思路:
1、文件上传(需要满足的条件)
2、Excel的文件类型(xls、xlsx)
3、读取Excel(Excel组成:sheet、行、单元格)
导入数据哪里来?
用户给的,需要上传
方法上必须有MultipartFile file
这个数据未来要去往那里?
DB
数据如何进入数据库?
插入n条,要用到批量插入的语句
插入的数据应该是List集合
数据库的方法的入参肯定是一个集合,集合中一般都是实体类
工具类要解析Excel文件
流
工具类如何写?
参数:流、文件类型、list中要存放的什么类型
返回值:List
Excel导出思路:
1、文件下载(需要满足的条件)
2、Excel的文件类型(xls、xlsx)
3、创建Excel(Excel组成:sheet、行、单元格)
数据哪里来?
DB查询
查询到的是什么类型?
List
Excel里面有什么?
标题:实体类提供
数据:List集合
工具类如何写?
参数:list集合、实体类的类型、文件类型、sheet名字
返回值:流
SSM
Struts2 + Spring + MyBatis
SpringMVC + Spring + MyBatis
1、每个技术需要用到的配置:
SpringMVC:前后台交互的桥梁
spring-mvc.xml 配置控制层注解扫描、视图解析器等组件
Spring:容器,用于框架之间的整合
spring-config.xml 配置业务层、持久层注解扫描、增强通知等
spring-dao.xml 配置数据库、事务、MyBatis整合
MyBatis:持久层框架,和数据库交互
数据库配置
映射文件
实体类别名
......
2、由于是web项目,希望在tomcat启动时加载所有的配置,所以需要在web.xml中配置。
通过web容器 加载SpringMVC容器和Spring容器
maven依赖
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.2.1.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.2.1.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-txartifactId>
<version>5.2.1.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>5.2.1.RELEASEversion>
dependency>
<dependency>
<groupId>commons-fileuploadgroupId>
<artifactId>commons-fileuploadartifactId>
<version>1.4version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.3version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>2.0.1version>
dependency>
<dependency>
<groupId>ojdbcgroupId>
<artifactId>ojdbcartifactId>
<version>6.0version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.21version>
dependency>
dependencies>
spring-mvc.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.soft"/>
<mvc:annotation-driven/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value=""/>
<property name="suffix" value=".jsp"/>
bean>
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="-1"/>
<property name="defaultEncoding" value="UTF-8"/>
bean>
beans>
spring-config.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.soft"/>
beans>
spring-dao.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:property-placeholder location="classpath*:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.uname}"/>
<property name="password" value="${jdbc.pwd}"/>
bean>
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations"
value="classpath*:com/soft/mapper/*.xml"/>
<property name="typeAliasesPackage" value="com.soft.entity"/>
bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.soft.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sessionFactory"/>
bean>
beans>
web.xml
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>SpringMVCservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath*:spring-mvc.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>SpringMVCservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath*:spring-*.xmlparam-value>
context-param>
<filter>
<filter-name>encodingfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>UTF-8param-value>
init-param>
filter>
<filter-mapping>
<filter-name>encodingfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
web-app>