在项目中学习:目标(实现什么样的目标)>>>思路(思路是怎样)>>>代码(具体的代码体现)
0.总体目标
1.数据库创建
CREATE DATABASE project_crowd CHARACTER SET utf8;
2.切换数据库
USE project_crowd;
3.创建数据库表
drop table if exists t_admin; # 如果存在t_admin则删除存在的表
CREATE TABLE t_admin (
id INT NOT NULL auto_increment, # 主键
login_acct VARCHAR ( 255 ) NOT NULL, # 登录账号
user_pswd CHAR ( 32 ) NOT NULL, # 登录密码
user_name VARCHAR ( 255 ) NOT NULL, # 昵称
email VARCHAR ( 255 ) NOT NULL, # 邮件地址
create_time CHAR ( 19 ), # 创建时间
PRIMARY KEY ( id ) # 设置主键
);
4.基于Maven的逆向工程(根据已存在的表,在项目中逆向生成对应的实体类、Mapper文件、Mapper接口)
1.在reverse模块中进行逆向:(在reverse模块的pom.xml中导入依赖)
<!-- 控制Maven在构建过程中的相关配置 -->
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.0</version>
<dependencies>
<!-- 依赖mybatis核心包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!-- 逆向工程核心依赖 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.2</version>
</dependency>
<!--MySQL驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
2.编写generatorConfig.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="zhTables" targetRuntime="MyBatis3">
<commentGenerator>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!-- 数据库链接URL、用户名、密码 -->
<jdbcConnection
driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/project_crowd?serverTimezone=UTC"
userId="root"
password="123456">
</jdbcConnection>
<!--
默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer
true,把JDBC DECIMAL 和 NUMERIC 类型解析为java.math.BigDecimal
-->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!--
生成model模型,对应的包路径,以及文件存放路径(targetProject),targetProject可以指定具体的路径,如./src/main/java,
也可以使用“MAVEN”来自动生成,这样生成的代码会在target/generatord-source目录下
-->
<!--<javaModelGenerator targetPackage="com.joey.mybaties.test.pojo" targetProject="MAVEN">-->
<javaModelGenerator targetPackage="crowd.entity" targetProject=".\src\main\java">
<!--是否让schema作为包的后缀-->
<property name="enableSubPackages" value="false"/>
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!--对应的mapper.xml文件 -->
<sqlMapGenerator targetPackage="mapper" targetProject=".\src\main\java">
<!--是否让schema作为包的后缀-->
<property name="enableSubPackages" value="false"/>
</sqlMapGenerator>
<!-- 对应的Mapper接口类文件 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="org.fall.mapper" targetProject=".\src\main\java">
<!--是否让schema作为包的后缀-->
<property name="enableSubPackages" value="false"/>
</javaClientGenerator>
<!-- 数据库表名与需要的实体类对应映射的指定 -->
<table tableName="t_admin" domainObjectName="Admin"/>
</context>
</generatorConfiguration>
3.IDEA进行逆向工程
- 选择菜单栏Run ->edit Cofiguerations—->左上角的+号—-》选择maven:如图
如下表示成功:
查看生成的目录结构:
运行完后,应当对产生的所有文件复制到对应的地方(Mapper接口放入component的mapper包下;实体类放入entity模块的entity包;xxxMapper.xml放入webui的resources文件夹下(xml放在web模块下方便寻找))
5.通过父工程管理依赖版本
在父工程通过dependencyManagement标签管理依赖版本,但是在子工程正式通过dependencies标签导入依赖前,这些依赖并不会生效
<!--通过properties标签指定一些需要重用的版本号,方便在后面调用-->
<properties>
<fall.spring.version>4.3.20.RELEASE</fall.spring.version>
<fall.spring.security.version>4.2.10.RELEASE</fall.spring.security.version>
</properties>
<!--依赖管理-->
<dependencyManagement>
<dependencies>
<!-- Spring 依赖 -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${fall.spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${fall.spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${fall.spring.version}</version>
</dependency>
<!-- Spring AOP -->
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>
<!-- 数据库依赖 -->
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
<!-- 数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.8</version>
</dependency>
<!-- MyBatis 与 Spring 整合 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.2</version>
</dependency>
<!-- MyBatis 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>4.0.0</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- 其他日志框架的中间转换包 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>1.7.25</version>
</dependency>
<!-- Spring 进行 JSON 数据转换依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.0</version>
</dependency>
<!-- JSTL 标签库 -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- junit 测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- 引入 Servlet 容器中相关依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<!-- JSP 页面使用的依赖 -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1.3-b06</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
<!-- SpringSecurity 对 Web 应用进行权限管理 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${fall.spring.security.version}</version>
</dependency>
<!-- SpringSecurity 配置 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${fall.spring.security.version}</version>
</dependency>
<!-- SpringSecurity 标签库 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>${fall.spring.security.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
6.Spring整合MyBatis
1.配置Maven依赖
在component模块的pom.xml配置一些必要的依赖
<dependencies> <!--依赖entity--> <dependency> <groupId>org.example</groupId> <artifactId>crowdfunding04-admin-entity</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!--依赖util--> <dependency> <groupId>org.example</groupId> <artifactId>crowdfunding05-common-util</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- Spring 依赖 --> <!-- https://mvnrepository.com/artifact/org.springframework/spring-orm --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <exclusions> <exclusion> <artifactId>commons-logging</artifactId> <groupId>commons-logging</groupId> </exclusion> </exclusions> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> </dependency> <!-- Spring AOP --> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/cglib/cglib --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> </dependency> <!-- 数据库依赖 --> <!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- 数据源 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> </dependency> <!-- MyBatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> </dependency> <!-- MyBatis 与 Spring 整合 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> </dependency> <!-- MyBatis 分页插件 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> </dependency> <!-- Spring 进行 JSON 数据转换依赖 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> <!-- JSTL 标签库 --> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson --> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> </dependency> </dependencies>
2.在webui模块中增加jdbc.properties文件
jdbc.user=root jdbc.password=123456 jdbc.url=jdbc:mysql://localhost:3306/project_crowd?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8 jdbc.driver=com.mysql.cj.jdbc.Driver
3.在webui模块增加spring-persist-mybatis.xml
```xml
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--数据源配置-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
</bean>
<a name="k0Iin"></a>
#### 4.在test包下编写测试程序
```java
package com.zh;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
//Spring整合Junit
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-persist-mybatis.xml"})
public class CrowdTest1 {
@Autowired
DataSource dataSource;
@Test
public void test01() throws SQLException {
//ApplicationContext context = new ClassPathXmlApplicationContext("spring-persist-mybatis.xml");
//DataSource dataSource = context.getBean(DataSource.class);
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
}
5.webui模块项目结构
6.测试添加数据
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-persist-mybatis.xml"})
public class CrowdTest1 {
@Autowired
DataSource dataSource;
@Autowired
AdminMapper adminMapper;
@Test
public void test02(){
Admin admin = new Admin(null,"tom", "123456", "汤姆", "tom@qq.com", null);
int i = adminMapper.insert(admin);
System.out.println(i);
}
}
7.一些需要注意的问题
- 查看各个模块jar包的依赖是否正确
- 查看逆向工程生成的实体类文件是否包含构造方法及该文件是否正确
- 查看逆向工程生成的XXXmapper.xml文件其中的实体类包是否对应正确
- 查看逆向工程生成的XXXmapper.xml文件操作数据库语句是否重复,保证没有重复代码
查看spring-persist-mybatis.xml文件中关于mybatis与mapper包对应是否正确
7.配置日志系统
使用日志的原因:
在实际开发中,如果在所有想查看的地方都通过System.out来打印,会给项目上线带来很多问题 System.out本质是一个IO操作,通常IO操作比较消耗性能。如果项目中过多的System.out,则可能对性能造成影响,而如果使用日志系统,就可以通过控制日 志级别,来批量控制打印信息。
这里使用slf4j+logback代替Spring默认使用的commons-loggin日志包。1.在component模块中增加依赖
<!-- 日志 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </dependency> <!-- 其他日志框架的中间转换包 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jul-to-slf4j</artifactId> </dependency>
2.测试使用日志打印
```java @Test public void test03(){ //获取Logger对象,这里传入的Class就是当前打印日志的类 Logger logger = LoggerFactory.getLogger(TestConnection.class); //等级 DEBUG < INFO < WARN < ERROR logger.debug(“I am DEBUG!!!”);
logger.info(“I am INFO!!!”);
logger.warn(“I am WARN!!!”);
logger.error(“I am ERROR!!!”);
}
<a name="krbaI"></a>
#### 3.关于在IDEA中如何排除jar包
在pom.xml文件中右键<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/23040397/1641142567877-aec75413-0562-4de8-af5c-88964bd073ef.png#clientId=ud82f61c0-2316-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=430&id=u5f9c4860&margin=%5Bobject%20Object%5D&name=image.png&originHeight=859&originWidth=1382&originalType=binary&ratio=1&rotation=0&showTitle=false&size=148104&status=done&style=none&taskId=u7c3c7e83-c290-4c8f-8169-d28244a4efd&title=&width=691)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/23040397/1641142641665-7869b5a6-048a-482d-9d6a-307522f989b5.png#clientId=ud82f61c0-2316-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=392&id=u1d3ec0a2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=784&originWidth=1099&originalType=binary&ratio=1&rotation=0&showTitle=false&size=53188&status=done&style=none&taskId=u67287d0d-58cf-42c3-b321-76e7ce3860d&title=&width=549.5)
<a name="chFXf"></a>
#### 4.配置管理日志logback.xml文件
```xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<!-- 指定日志输出的位置 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 日志输出的格式 -->
<!-- 按照顺序分别是: 时间、 日志级别、 线程名称、 打印日志的类、 日志主体
内容、 换行 -->
<pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%-8thread] [%logger] [%msg]%n</pattern>
</encoder>
</appender>
<!-- 设置全局日志级别。 日志级别按顺序分别是: DEBUG、 INFO、 WARN、 ERROR -->
<!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
<root level="INFO">
<!-- 指定打印日志的 appender, 这里通过"STDOUT"引用了前面配置的 appender -->
<appender-ref ref="STDOUT" />
</root>
<!-- 根据特殊需求指定局部日志级别 -->
<logger name="com.zh.crowd.mapper" level="DEBUG"/>
</configuration>
8.配置声明式事务
配置声明式事务的目的:希望指定的事务方法中,如果存在多个数据库操作,则要么一起提交,要么一起回滚(rollback),即:事务方法中多个数据库操作,只要有一个失败,则所有操作回滚。
- 开启事务
- 执行数据库操作
- 提交事务
- 回滚事务
- 关闭资源
1.配置文件的结构图解
2.声明式事务所依赖的包
(前面父工程中已经加入了Maven依赖,不需要多次操作了)<!-- Spring AOP --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> </dependency>
3.配置针对事务的Spring配置文件:spring-persist-tx.xml
事务方法一般定义在service层 ```xml <?xml version=”1.0” encoding=”UTF-8”?>
<!--将com.zh.crowd.service包中的组件扫描入容器,因为事务方法一般定义在service层-->
<!--配置自动扫描的包:主要是为了把Service扫描到IOC容器中-->
<context:component-scan base-package="com.zh.crowd.service"/>
<!--配置事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!--name属性指定当前要配置的事务方法的方法名,符合名字的配置对应规则-->
<!--查询方法通常设置为只读,便于数据库根据只读属性进行性能优化-->
<tx:method name="get*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="count*" read-only="true"/>
<!--涉及增删改查操作的方法的配置-->
<!--propagation属性配置事务方法的传播行为-->
<!--
默认行为:REQUIRED,表示当前方法必须运行在事务中,如果没有事务,则开启事务,在自己的事务中运行。
如果已经有了已开启的事务,则在当前事务中运行。有可能和其他方法共用同一个事务
建议设置:REQUIRES_NEW,表示当前方法必须运行在事务中,如果没有事务,则开启事务,在自己的事务中运行。
和 REQUIRED 的区别是就算现在已经有了已开启的事务,也一定要开启自己的事务,避免和其他方法共用同一个事务。
-->
<!--rollback-for:表示触发什么异常时,进行回滚;默认值:运行时异常,建议设置为运行时异常+编译期异常-->
<tx:method name="save*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>
<tx:method name="update*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>
<tx:method name="remove*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>
<!--需要了解的是,如果方法名没有匹配的name,则该方法的事务不会生效-->
</tx:attributes>
</tx:advice>
<a name="TZxFy"></a>
#### 4.测试事务
配置完成后,在com.zh.crowd.service包下,进行数据库操作时,触发异常,即发生回滚,不会依然执行
1. 在component模块中创建包
![image.png](https://cdn.nlark.com/yuque/0/2022/png/23040397/1641147831871-2025f404-9d19-4b3b-807e-34580722f926.png#clientId=u37f64b97-3e8f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=267&id=u166083a0&margin=%5Bobject%20Object%5D&name=image.png&originHeight=534&originWidth=621&originalType=binary&ratio=1&rotation=0&showTitle=false&size=26158&status=done&style=none&taskId=u997a4acd-1554-4dd4-9480-56f77745378&title=&width=310.5)
2. 编写对应代码
- AdminService.java接口文件
```java
package com.zh.crowd.service.api;
import com.zh.crowd.entity.Admin;
public interface AdminService {
void saveAdmin(Admin admin);
}
- 编写AdminServiceImpl实现类文件 ```java package com.zh.crowd.service.impl;
import com.zh.crowd.entity.Admin; import com.zh.crowd.mapper.AdminMapper; import com.zh.crowd.service.api.AdminService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;
@Service public class AdminServiceImpl implements AdminService {
@Autowired
private AdminMapper adminMapper;
@Override
public void saveAdmin(Admin admin) {
adminMapper.insert(admin);
}
}
3. 编写一个测试方法
```java
@Autowired
AdminService adminService;
@Test
public void text03(){
Admin admin = new Admin(null,"tom1", "1234561", "汤姆1", "tom@qq.com1", null);
adminService.saveAdmin(admin);
}
5.注意问题
在基于XML的声明式事务中事务属性的tx:method是必须配置的,如果某个方法没有配置对应的tx:method,那么事务对这个方法就不会生效。就是该配置文件spring-persist-tx.xml的tx:method。
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!--name属性指定当前要配置的事务方法的方法名,符合名字的配置对应规则-->
<!--查询方法通常设置为只读,便于数据库根据只读属性进行性能优化-->
<tx:method name="get*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="count*" read-only="true"/>
<!--涉及增删改查操作的方法的配置-->
<!--propagation属性配置事务方法的传播行为-->
<!--
默认行为:REQUIRED,表示当前方法必须运行在事务中,如果没有事务,则开启事务,在自己的事务中运行。
如果已经有了已开启的事务,则在当前事务中运行。有可能和其他方法共用同一个事务
建议设置:REQUIRES_NEW,表示当前方法必须运行在事务中,如果没有事务,则开启事务,在自己的事务中运行。
和 REQUIRED 的区别是就算现在已经有了已开启的事务,也一定要开启自己的事务,避免和其他方法共用同一个事务。
-->
<!--rollback-for:表示触发什么异常时,进行回滚;默认值:运行时异常,建议设置为运行时异常+编译期异常-->
<tx:method name="save*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>
<tx:method name="update*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>
<tx:method name="remove*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>
<!--需要了解的是,如果方法名没有匹配的name,则该方法的事务不会生效-->
</tx:attributes>
</tx:advice>
9.表述层环境搭建
1.web.xml与spring配置文件的关系(思路)
2.在webui模块中配置web.xml文件
- 项目结构如图
配置web.xml文件 ```xml <?xml version=”1.0” encoding=”UTF-8”?>
ZH contextConfigLocation classpath:spring-persist-*.xml org.springframework.web.context.ContextLoaderListener CharacterEncodingFilter org.springframework.web.filter.CharacterEncodingFilter encoding UTF-8 forceRequestEncoding true forceResponseEncoding true CharacterEncodingFilter /* springDispatcherServlet org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:spring-web-mvc.xml 1 springDispatcherServlet *.html <!— 响应状态码:
200:成功 302:重定向 400:传参问题 404:错误
—>
<a name="iSAbB"></a>
#### 3.配置SpringMVC配置文件:spring-web-mvc.xml
1. 在component模块创建包
![image.png](https://cdn.nlark.com/yuque/0/2022/png/23040397/1641195741695-364e2ec9-11b5-4368-b4e7-52704b171a99.png#clientId=u53510911-4f40-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=259&id=u2e88db5d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=518&originWidth=777&originalType=binary&ratio=1&rotation=0&showTitle=false&size=25802&status=done&style=none&taskId=u424b38a1-f891-4f01-bf45-f6a5acfadc4&title=&width=388.5)
2. 配置spring-web-mvc.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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--配置包扫描-->
<context:component-scan base-package="com.zh.crowd.mvc"/>
<!--配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!--启动注解驱动-->
<mvc:annotation-driven/>
</beans>
4.测试SSM整合是否成功
webui模块添加依赖
<!-- 引入 Servlet 容器中相关依赖 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <scope>provided</scope> </dependency> <!-- JSP 页面使用的依赖 --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <scope>provided</scope> </dependency>
Controller控制器代码
@Controller public class TestHandler { @Autowired AdminService adminService; @RequestMapping("/test/ssm.html") public String testSSM(Model model){ //Admin admin = adminService.queryAdmin(1); List<Admin> admins = adminService.getAll(); model.addAttribute("admins", admins); return "target"; } }
前端代码
index.jsp文件代码如下:
<%--
Created by IntelliJ IDEA.
User: Dimple
Date: 2022/1/3
Time: 15:51
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%-- 一、无base标签:--%>
<a href="${pageContext.request.contextPath}/test/ssm.html">测试页面</a>
<%-- 二、有base标签:--%>
<head>
<base href="http://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/">
</head>
<body>
<a href="test/ssm.html">测试页面</a>
</body>
</body>
</html>
Base标签的要求:
- base 标签必须写在 head 标签内部 。
- base 标签必须在所有“带具体路径”的标签的前面。
- serverName 部分 EL 表达式和 serverPort 部分 EL 表达式之间必须写“:”。端口号前面的“:”不能省略。
- contextPath前面不能写“/”
- comtextPath后面必须写“/”
- 页面上使用base标签的前面都不能写“/”
- serverPort 部分 EL 表达式和 contextPath 部分 EL 表达式之间绝对不能写“/”。 原因:
- contextPath 部分 EL 表达式本身就是“/”开头。
- 如果多写一个“/”会干扰 Cookie 的工作机制 serverPort 部分 EL 表达式后面必须写“/”。
target.jsp文件代码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
${requestScope.admins}
</body>
</html>
- 成功如下:
- 解决IDEA中Tomcat打印乱码
10.SpringMVC环境下的Ajax请求
1.建立意识
普通请求 ——->>> handler处理 ——>>> 页面
Ajax请求 ———>>>handler处理 ——>>>Json
2.常用注解
@ResponseBody和RequestBody要正常工作必须要Jackson的支持,要确认项目中引入如下依赖:
<!-- Spring 进行 JSON 数据转换依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
3.加入jQuery
4.前端代码:index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%-- 一、无base标签:--%>
<a href="${pageContext.request.contextPath}/test/ssm.html">测试页面</a>
<%-- 二、有base标签:--%>
<head>
<base href="http://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/">
</head>
<a href="test/ssm.html">测试页面</a>
<button id="btn1">Test Ajax One</button>
<br/><br/>
<button id="btn2">Test Ajax Two</button>
<br/><br/>
<button id="btn3">Test Compose Object</button>
<br/><br/>
<button id="btn4">Test ResultEntity</button>
</body>
<script src="jquery/jquery-3.4.1.js" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
//btn1
//此方式可以在浏览器看到发送的请求体是Form Data(表单数据)
$("#btn1").click(function () {
$.ajax({
url: "send/array/one.html", //请求目标资源地址
type: "post", //请求方式
data: arrayStr, //发送的请求参数
dataType: "text", //表示如何对待服务器返回的数据
success: function (response) {
alert(response);
},
error: function (response) {
alert(response);
}
});
});
//btn2
//此方式可以在浏览器看到发送的请求体是Request Payload(请求负载)
$("#btn2").click(function () {
//准备要发送的数据
var array=[5,8,12];
//必须先将目标转换成JSON字符串
var arrayStr = JSON.stringify(array);
$.ajax({
url: "send/array/two.html",
type: "post",
data: arrayStr,
dataType: "text",
contentType: "application/json;charset=UTF-8", //告诉服务器端当前请求的请求体是JSON格式
success: function (response) {
alert(response);
},
error: function (response) {
alert(response);
}
});
});
//btn3
//传输复杂对象
$("#btn3").click(function () {
var student = {
"name":"Fall",
"id":21,
"address":{
"province":"浙江",
"city":"宁波"
},
"subjects":[
{
"subjectName":"Java",
"score":96
},
{
"subjectName":"Data Struct",
"score":93
}
],
"map":{
"key1":"value1",
"key2":"value2"
}
}; //student end
var studentStr = JSON.stringify(student);
$.ajax({
url: "send/compose/object.html",
type: "post",
data: studentStr,
dataType: "text",
contentType: "application/json;charset=UTF-8",
success: function (response) {
alert(response); //在浏览器控制台打印返回的信息
},
error: function (response) {
alert(response);
}
});
}); //btn3
//btn4
//使用ResultEntity,统一返回的格式
$("#btn4").click(function () {
var student = {
//...与前面相同
};
var studentStr = JSON.stringify(student);
$.ajax({
url: "send/compose/object.json", //此时是json,表示返回的数据是json格式的
type: "post",
data: studentStr,
dataType: "json", //此时服务端返回的数据是json格式
contentType: "application/json;charset=UTF-8",
success: function (response) {
console.log(response); //在浏览器控制台打印返回的信息
},
error: function (response) {
console.log(response);
}
});
});
//btn4
});
</script>
</html>
5.对应的控制层的方法:TestHandler.java
//通过@RequestParam接收数组
@ResponseBody
@RequestMapping("/send/array/one.html")
public String testAjax01(@RequestParam("array") Integer[] array){
for(Integer num : array){
System.out.println("num:"+num);
}
return "success";
}
//通过@RequestBody接收数组
@ResponseBody
@RequestMapping("/send/array/two.html")
public String testAjax02(@RequestBody Integer[] array){
for(Integer num : array){
System.out.println("num:"+num);
}
return "success";
}
//通过@RequestBody接收复杂对象
@ResponseBody
@RequestMapping("/send/compose/object.html")
public String testSendComposeObject(@RequestBody Student student){
System.out.println(student);
return "success";
}
//通过一个工具类(ResultEntity<T>)统一返回数据的格式
@ResponseBody
@RequestMapping("/send/compose/object.json")
public ResultEntity<Student> testResultEntity(@RequestBody Student student){
return ResultEntity.successWithData(student);
}
6.注意问题
==如果希望通过@RequestBody接收前端发来的JSON数据,则前端发来的数据需要进行以下几步:==
- 准备好要发送的数据(需要是JSON对象或JSON数组)
- 通过JSON.stringify()方法,转换成JSON字符串
- 将JSON字符串直接赋值给data属性
- 必须设置contentType: “application/json;charset=UTF-8”
此时后端在加入了json依赖,并开启mvc注解驱动,就可以使用RequestBody接收JSON数据。
11.统一后端返回数据格式
1.工具类项目结构
2.工具类ResultEntity.java代码
public class ResultEntity<T> {
//设置两个常量
public static final String SUCCESS = "SUCCESS";
public static final String FAILED = "FAILED";
//请求错误时,返回的错误信息,对应SUCCESS与FAILED
private String message;
//要返回的数据
private T data;
//封装当前请求的处理结果是成功还是失败
private String result;
//请求处理成功并且不向前端返回数据时,使用的静态方法
//第一个<Type>表示声明一个泛型Type,第二个和return中的<Type>表示使用该泛型
public static <Type> ResultEntity<Type> successWithoutData(){
return new ResultEntity<Type>(null,null,SUCCESS);
}
//请求处理成功并且向前端返回数据时,使用的静态方法
public static <Type> ResultEntity<Type> successWithData(Type data){
return new ResultEntity<Type>(null,data,SUCCESS);
}
//请求处理失败,需要返回错误信息时,使用的静态方法
public static <Type> ResultEntity<Type> failed(String message){
return new ResultEntity<>(message,null,FAILED);
}
/** Getter,Setter,constructor and toString **/
}
3.注意问题
工具类中静态方法的泛型Type,代表了在前端请求后,后端返回的数据的类型;只是因为静态方法中不能调用非静态的属性,因此需要重新声明一个泛型,名字为Type(其他也可以)。
即想要返回一个Student对象时,就通过方法。
@RequestMapping(...)
@ResponseBody
public ResultEntity<Student> getStudent(){
//...例如进行数据库查询操作等
return ResultEntity.successWithData(student);
}
12异常映射
1.目标
2.思路
- 注意:springMVC提供了基于XML和注解两种异常映射机制。
3.基于XML的异常映射机制在spring-web-mvc.xml配置
<!--基于XML的异常映射-->
<!-- <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver" id="simpleMappingExceptionResolver">-->
<!-- <property name="exceptionMappings">-->
<!-- <props>-->
<!-- <!– <prop key="java.lang.Exception">system-error</prop>–>-->
<!-- <!– 通过xml配置AccessForbiddenException的异常映射 –>-->
<!-- <prop key="org.fall.exception.AccessForbiddenException">admin-login</prop>-->
<!-- </props>-->
<!-- </property>-->
<!-- </bean>-->
4.判断请求类型的工具方法
- 判断请求类型的工具方法之加入依赖
依赖如下:
<dependencies>
<!-- 引入 Servlet 容器中相关依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
</dependencies>
- 判断请求类型的工具方法之如何判断请求是否为Json
前端浏览器开发者工具中查看,只要满足一个就是Json请求。
- 判断请求类型的工具方法之创建方法
代码:
package com.zh.crowd.util;
import javax.servlet.http.HttpServletRequest;
public class CrowdUtil {
/**判断当前请求是否为Ajax请求
* @param request
* @return true==json请求 ; false==普通页面请求
*/
public static boolean judgeRequestType(HttpServletRequest request){
//获取请求消息头
String accept = request.getHeader("Accept");
String header = request.getHeader("X-Requested-With");
//判断
return (accept != null && accept.contains("application/json"))
||
(header != null && header.equals("XMLHttpRequest"));
}
}
5.基于注解的异常映射机制
- 项目结构
- 这里通过使用前面编写的工具类的判断方法,实现对不同请求类型进行不同处理。(代码) ```java package com.zh.crowd.mvc.config;
import com.google.gson.Gson; import com.zh.crowd.constant.CrowdConstant; import com.zh.crowd.util.CrowdUtil; import com.zh.crowd.util.ResultEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;
//@ControllerAdvice注解标明该类是基于注解的异常处理器类 @ControllerAdvice public class CrowdExceptionResolver {
//处理空指针异常
//@ExceptionHandler将一个具体的异常类型和一个方法关联起来
@ExceptionHandler(value = {NullPointerException.class})
public ModelAndView resolveNullPointerException(
//实际捕获到的异常类型
NullPointerException exception,
//当前请求对象
HttpServletRequest request,
HttpServletResponse response
) throws IOException {
return commonCode(exception,request,response,"system-error");
}
//处理数学异常,这里如果内部操作相同,跳转页面也相同,其实可以放在上面一个方法中,此处只是为了演示
@ExceptionHandler(value = {ArithmeticException.class})
public ModelAndView resolveArithmeticException(ArithmeticException exception,
HttpServletRequest request,HttpServletResponse response) throws IOException {
return commonCode(exception,request,response,"system-error");
}
//整理出的不同异常的可重用代码
private ModelAndView commonCode(
//触发的异常,此处借助多态
Exception exception,
//客户器端的请求
HttpServletRequest request,
//服务端的响应
HttpServletResponse response,
//指定普通页面请求时,去的错误页面
String viewName
) throws IOException {
//1.判断当前请求类型
boolean judgeRequestType = CrowdUtil.judgeRequestType(request);
//2.如果是Ajax请求
if (judgeRequestType){
//if判断-是json请求
//3.创建ResultEntity
ResultEntity<Object> failed = ResultEntity.failed(exception.getMessage());
//4.创建Gson对象
Gson gson = new Gson();
//5.将ResultEntity对象转换成json格式
String json = gson.toJson(failed);
//通过原生servlet的response传回异常内容
//6.将Json字符串作为响应体返回给浏览器
response.getWriter().write(json);
//此时只需要返回null(因为是通过json格式返回数据)
//7.由于上面已经通过原生的response对象返回了响应,所以不通过ModelAndView对象
return null;
} else {
//if判断-是普通页面请求
//创建ModelAndView对象
//8.如果不是Ajax请求则创建ModelAndView对象
ModelAndView modelAndView = new ModelAndView();
//设置触发异常跳转的页面(会自动被视图解析器加上前后缀)
//9.将Exception对象存入模型
modelAndView.setViewName(viewName);
//将异常信息加入
modelAndView.addObject(CrowdConstant.ATTR_NAME_EXCEPTION, exception);
//返回设置完成的ModelAndView
return modelAndView;
}
}
}
<a name="oOxwF"></a>
### 13.以常量管理常用的属性
对于经常使用到且存在复用的属性名,可以单独设置一个常量类。这样的好处是可以减少因拼写导致的错误。
<a name="sFABJ"></a>
#### 1.项目结构
![image.png](https://cdn.nlark.com/yuque/0/2022/png/23040397/1641222737088-212e14af-cafe-4930-8e51-52f17debee3e.png#clientId=u3d533a62-0042-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=213&id=u2e9940e7&margin=%5Bobject%20Object%5D&name=image.png&originHeight=425&originWidth=636&originalType=binary&ratio=1&rotation=0&showTitle=false&size=20073&status=done&style=none&taskId=uf9b71b97-52bc-47d2-aa79-67ab03818c0&title=&width=318)
<a name="bH7y2"></a>
#### 2.代码
```java
//常量类
public class CrowdConstant {
public static final String ATTR_NAME_EXCEPTION = "exception";
public static final String MESSAGE_LOGIN_FAILED = "登录失败!请确认账号密码是否正确";
}
14.前端页面引入
1.引入需要的静态资源
2.引入admin-login.jsp
将登录页面的代码复制入创建的admin-login.jsp文件,并进行适当的修改(还未完全修改)
主要是修改了编码方式为统一的UTF-8;添加了base标签;提前设置form表单的action和method;设置了账号密码的name属性。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="keys" content="">
<meta name="author" content="">
<base href="http://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/">
<link rel="stylesheet" href="bootstrap/css/bootstrap.css">
<link rel="stylesheet" href="css/font-awesome.min.css">
<link rel="stylesheet" href="css/login.css">
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<div><a class="navbar-brand" href="index.html" style="font-size:32px;">尚筹网-创意产品众筹平台</a></div>
</div>
</div>
</nav>
<div class="container">
<form action="admin/login/login.html" method="post" class="form-signin" role="form">
<h2 class="form-signin-heading"><i class="glyphicon glyphicon-log-in"></i> 用户登录</h2>
<div class="form-group has-success has-feedback">
<input type="text" name="login-user" class="form-control" id="inputSuccess4" placeholder="请输入登录账号" autofocus>
<span class="glyphicon glyphicon-user form-control-feedback"></span>
</div>
<div class="form-group has-success has-feedback">
<input type="text" name="login-pwd" class="form-control" id="inputSuccess4" placeholder="请输入登录密码" style="margin-top:10px;">
<span class="glyphicon glyphicon-lock form-control-feedback"></span>
</div>
<div class="checkbox" style="text-align:right;"><a href="reg.html">我要注册</a></div>
<button type="submit" class="btn btn-lg btn-success btn-block">登录</button>
</form>
</div>
<script src="jquery/jquery-2.1.1.min.js"></script>
<script src="bootstrap/js/bootstrap.min.js"></script>
</body>
</html>
3.在spring-web-mvc.xml配置路径
在mvc配置文件中通过view-controller,使通过path指定的路径,访问登录页面(因为只需要单纯的访问而不需要附带数据等操作,因此使用此方法相比handler方法转发更加方便)
<!--基于XML的异常映射-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver" id="simpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.Exception">system-error</prop>
<!-- 通过xml配置AccessForbiddenException的异常映射 -->
<!-- <prop key="org.fall.exception.AccessForbiddenException">admin-login</prop>-->
</props>
</property>
</bean>
<!--通过view-controller 来设置一些直接的页面跳转-->
<!--去登录页面-->
<mvc:view-controller path="/admin/login/page.html" view-name="admin-login"/>
4.测试访问是否成功
http://localhost:8080/zhcrowdfunding01_admin_webui_war_exploded//admin/login/page.html
15.使用layer弹层组件
1.网络下载layer.js
2.引入layer组件
3.引入注意
注意:layer组件依赖于jquery,因此layer的引入必须在jquery之后,否则会出错。
<%@ page contentType="text/html;charset=UTF-8" language="java"
pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
<base href="http://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/">
<link rel="stylesheet" href="bootstrap/css/bootstrap.css">
<script type="text/javascript" src="jquery/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="layer/layer.js"></script>
</head>
<body>
<button id="btn5">点一下试试</button>
</body>
<script type="text/javascript">
$(function () {
$("#btn5").click(function () {
layer.msg("test layer");
//alert("测试");
});
});
</script>
</html>
4.layer弹窗效果
5.修改修饰错误页面:system-error.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="keys" content="">
<meta name="author" content="">
<base href="http://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/">
<link rel="stylesheet" href="bootstrap/css/bootstrap.css">
<link rel="stylesheet" href="css/font-awesome.min.css">
<link rel="stylesheet" href="css/login.css">
<script src="jquery/jquery-3.4.1.js" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
$("button").click(function () {
window.history.back();
});
});
</script>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<div><a class="navbar-brand" href="index.html" style="font-size:32px;">众筹平台</a></div>
</div>
</div>
</nav>
<div class="container">
<h1 style="text-align: center;">请求出现了错误QAQ</h1>
<h2 style="text-align: center;">错误消息:${requestScope.exception.message}</h2>
<br/><br/>
<button style="width:150px;margin: 0 auto;" class="btn btn-lg btn-success btn-block">返回上一步</button>
</div>
<script src="bootstrap/js/bootstrap.min.js"></script>
</body>
</html>