环境搭建
总体目标
一、项目结构搭建
模块关系
parent模块仅仅用来确定各个Maven依赖的版本
webui、component、entity模块继承自parent模块
util、reverse模块属于独立工程,不参与继承与聚合
且webui依赖于component,component依赖于entity、util。
各个工程的打包方式:
<!--parent-->
<groupId>org.example</groupId>
<artifactId>crowdfunding01-admin-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<!--webui-->
<packaging>war</packaging>
<!--其他的模块都是默认打包方式(jar)-->
IDEA中创建多模块工程:
先创建一个Empty Project(空工程),创建完成后一个个添加各个Maven模块
由于webui模块,需要打成war包,因此需要有web工程的目录结构,可以通过在Modules中添 加,产生对应的结构:
<packaging>pom</packaging>
<modules>
<module>crowdfunding02-admin-webui</module>
<module>crowdfunding03-admin-component</module>
<module>crowdfunding04-admin-entity</module>
</modules>
<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.0.31</version>-->
<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 依赖配置 -->
<!-- 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>
模块创建时应该选择好父模块
二、数据库构建与逆向
1、建表:
CREATE DATABASE project_rowd CHARACTER SET utf8;
USE project_rowd;
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 ) # 设置主键
);
进行基于Maven的逆向工程(根据已存在的表,在项目中逆向生成对应的实体类、Mapper文件、Mapper接口)
2、在reverse模块中进行逆向:
1)、pom.xml中导入依赖
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.8</version>
</dependency>
</dependencies>
<!-- 控制Maven在构建过程中的相关配置 -->
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.0</version>
<dependencies>
<!-- 逆向工程核心依赖 -->
<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>5.1.49</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="atguiguTables" 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_rowd?serverTimezone=UTC"
userId="root"
password="root">
</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>
在IDEA中进行逆向工程的方法:
运行完后,应当对产生的所有文件各归各位(Mapper接口放入component的mapper包下;实体类放入entity模块的entity包;xxxMapper.xml放入webui的resources文件夹下(xml放在web模块下方便寻找))
三、通过父工程管理依赖版本
在父工程通过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>
四、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>
**webui模块**配置依赖于component,并引入一些scope为test的依赖(如junit)
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>crowdfunding03-admin-component</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
</dependencies>
2、创建各配置文件
①resources/mybatis/**mybatis-config.xml**(Mybatis配置文件,可以省略)
<?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>
<!--Spring与MyBatis整合后,MyBatis的配置文件可有可不有-->
</configuration>
②resources/**spring-persist-mybatis.xml**(Spring配置文件,用于整合MyBatis)
<!--引入properties文件-->
<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>
<!--配置SqlSessionFactory(设置mybatis配置文件的路径、mapper.xml文件的路径、注入数据源)-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:mybatis/mapper/*.xml"/>
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置与mapper.xml对应的mapper接口的包路径-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="org.fall.mapper"/>
</bean>
③resources/**jdbc.properties**(存放数据库连接信息)
jdbc.user=root
jdbc.password=root
jdbc.url=jdbc:mysql://localhost:3306/project_rowd?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8
jdbc.driver=com.mysql.cj.jdbc.Driver
2、测试整合是否成功
/**
* RunWith与ContextConfiguration指定xml的作用与
* ApplicationContext context = new ClassPathXmlApplicationContext("spring-persist-mybatis.xml");类似
* 前者通过让测试在Spring容器环境下执行,使得DataSource可以被自动注入,后者导入Spring配置文件
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-persist-mybatis.xml"})
public class TestConnection {
@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);
}
可以打印出connection的数据,而不是报空指针异常,得出结论:整合成功。
五、配置日志系统
使用日志的原因:
在实际开发中,如果在所有想查看的地方都通过System.out来打印,会给项目上线带来很多问题<br /> 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、使用日志打印的方法
@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!!!");
}
六、配置声明式事务
配置声明式事务的**目的**:希望指定的事务方法中,如果存在多个数据库操作,则要么一起提交,要么一起回滚(rollback),即:事务方法中多个数据库操作,只要有一个失败,则所有操作回滚。
1、声明式事务所依赖的包
(前面已经加入了Maven依赖,不需要多次操作了)
<!-- Spring AOP -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
</dependency>
2、配置针对事务的Spring配置文件
spring-persist-tx.xml
事务方法一般定义在service层
<!--将org.fall.service包中的组件扫描入容器,因为事务方法一般定义在service层-->
<context:component-scan base-package="org.fall.service"/>
<!--配置事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置AOP-->
<aop:config>
<!--配置切入点表达式-->
<!--public void org.fall.service.impl.adminServiceImpl(Admin admin)-->
<aop:pointcut id="txPointcut" expression="execution(* *..*ServiceImpl.*(..))"/>
<!--关联事务通知与切入点-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
<!--配置事务通知-->
<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>
配置完成后,在org.fall.service包下,进行数据库操作时,触发异常,即发生回滚,不会依然执行
七、表述层环境搭建
首先需要确定已经导入了SpringMVC的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
1、配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!--配置ContextLoaderListener,加载Spring配置文件-->
<!--contextConfigLocation需要的内容-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-persist-*.xml</param-value>
</context-param>
<!--将ContextLoaderListener加入容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--配置CharacterEncodingFilter,解决乱码问题-->
<!--如果web.xml中存在多个Filter,则此Filter必须作为过滤器链的第一个Filter-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!-- 指定字符集编码 -->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<!--强制请求进行编码-->
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<!--强制响应进行编码-->
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<!--设置过滤器过滤的请求的路径(/*表示全部请求)-->
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--配置DispatcherServlet(即配置SpringMVC的前端控制器)-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--指定SpringMVC配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-web-mvc.xml</param-value>
</init-param>
<!--使DispatcherServlet在Web应用启动时就创建对象并初始化-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!--根据请求的扩展名决定是否交给SpringMVC来处理-->
<!--
请求的扩展名应与预计的响应体格式相同
要求json数据则后缀.json; 要求页面则后缀.html
-->
<url-pattern>*.html</url-pattern>
<url-pattern>*.json</url-pattern>
</servlet-mapping>
</web-app>
2、配置SpringMVC配置文件
<!--配置包扫描,这里将controller层放在mvc包下,因此配置扫描mvc-->
<context:component-scan base-package="org.fall.mvc"/>
<!--配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!--启动注解驱动-->
<mvc:annotation-driven/>
3、测试SSM整合是否成功
**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
<%-- 一、无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>
**Base标签的要求:**
base 标签必须写在 head 标签内部<br />base 标签必须在所有“带具体路径”的标签的前面<br /> serverName 部分 EL 表达式和 serverPort 部分 EL 表达式之间必须写“:”<br />serverPort 部分 EL 表达式和 contextPath 部分 EL 表达式之间绝对不能写“/”<br /> 原因:contextPath 部分 EL 表达式本身就是“/”开头,如果多写一个“/”会干扰 Cookie 的工作机制<br />serverPort 部分 EL 表达式后面必须写“/”
八、SpringMVC环境下的Ajax请求
检查依赖的包:
<!-- 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>
加入jQuery:
测试Ajax(基于jQuery):
jQuery代码:
<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>
对应的四个按钮:
<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>
对应的控制层的四个方法:
//通过@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);
}
如果希望通过@RequestBody接收前端发来的JSON数据,则前端发来的数据需要进行以下几步:
- 准备好要发送的数据(需要是JSON对象或JSON数组)
- 通过JSON.stringify()方法,转换成JSON字符串
- 将JSON字符串直接赋值给data属性
- 必须设置contentType: “application/json;charset=UTF-8”
此时后端在加入了json依赖,并开启mvc注解驱动,就可以使用RequestBody接收JSON数据。
统一后端返回数据格式
通过工具类,实现统一后端返回的数据格式
工具类代码:
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 **/
}
工具类中静态方法的泛型Type,代表了在前端请求后,后端返回的数据的类型;只是因为静态方法中不能调用非静态的属性,因此需要重新声明一个泛型,名 字为Type(其他也可以)。
即想要返回一个Student对象时,就通过方法
@RequestMapping(...)
@ResponseBody
public ResultEntity<Student> getStudent(){
//...例如进行数据库查询操作等
return ResultEntity.successWithData(student);
}
九、异常处理
目标:统一管理项目中的异常。
- 抛出异常
- 返回给前端异常信息
- 普通请求:在返回的页面上显示异常信息
- Ajax请求:返回JSON格式的数据
观察浏览器的开发者工具中,发现可以根据客户端发送的请求类型,区别是普通请求还是Ajax请求。
根据这一特点,编写一个工具类,其中一个静态方法用来判断请求是否是JSON请求
public class CrowdUtil {
/**
* @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"));
}
}
异常映射实现的方式
一、通过XML配置实现
在mvc的配置文件中将SimpleMappingExceptionResolver加入容器,在exceptionMappings中配置异常与错误页面的映射
但是此时并未实现区别JSON请求与普通请求的要求。
<!--基于XML的异常映射-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver" id="simpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.Exception">system-error</prop>
</props>
</property>
</bean>
二、通过注解实现
这里通过使用前面编写的工具类的判断方法,实现对不同请求类型进行不同处理。
//注解标明该类是基于注解的异常处理器类
@ControllerAdvice
public class CrowdExceptionResolver {
//处理空指针异常
@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 {
boolean judgeRequestType = CrowdUtil.judgeRequestType(request);
if (judgeRequestType){
//if判断-是json请求
ResultEntity<Object> failed = ResultEntity.failed(exception.getMessage());
//创建Gson对象
Gson gson = new Gson();
//将ResultEntity对象转换成json格式
String json = gson.toJson(failed);
//通过原生servlet的response传回异常内容
response.getWriter().write(json);
//此时只需要返回null(因为是通过json格式返回数据)
return null;
} else {
//if判断-是普通页面请求
//创建ModelAndView对象
ModelAndView modelAndView = new ModelAndView();
//设置触发异常跳转的页面(会自动被视图解析器加上前后缀)
modelAndView.setViewName(viewName);
//将异常信息加入
modelAndView.addObject(CrowdConstant.ATTR_NAME_EXCEPTION, exception);
//返回设置完成的ModelAndView
return modelAndView;
}
}
}
十、以常量管理常用的属性
对于经常使用到且存在复用的属性名,可以单独设置一个常量类。这样的好处是可以减少因拼写导致的错误。
//常量类
public class CrowdConstant {
public static final String ATTR_NAME_EXCEPTION = "exception";
public static final String MESSAGE_LOGIN_FAILED = "登录失败!请确认账号密码是否正确";
}
十一、前端页面引入
引入登录页面
1、引入需要的静态资源
2、将登录页面的代码复制入创建的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>
遇到的奇怪的问题
在mvc配置文件中通过view-controller,使通过path指定的路径,访问登录页面(因为只需要单纯的访问而不需要附带数据等操作,因此使用此方法相比handler方法转发更加方便)
<!--通过view-controller 来设置一些直接的页面跳转-->
<mvc:view-controller path="/admin/login/page.html" view-name="admin-login"/>
但是最后测试下来,发现访问时会**发生404错误**:
我使用的默认是Edge Beta浏览器,当使用Edge浏览器时,可以通过链接访问登录页面,也可以直接使用`localhost:8888/admin/login/page.html`访问登录页面;但是当使用了chrome浏览器,使用地址栏输入...page.html时,发生奇怪的事情:报404错误,且之后edge也不能访问登录页面。最后尝试了很久,觉得应该是Tomcat缓存的问题,但尝试了多种清理缓存的方法,还是没有效果,最后**去掉了IDEA中对tomcat的一个勾选**:
这样可以算是“暂时”解决了问题。
尝试使用layer弹层组件
1、网上下载layer.js(此处是3.3.1版本)
2、引入与测试使用layer组件
注意:layer组件依赖于jquery,因此layer的引入必须在jquery之后,否则会出错。
<script src="jquery/jquery-3.4.1.js" type="text/javascript"></script>
<script src="layer/layer.js" type="text/javascript"></script>
<script>
$("#btn5").click(function () {
layer.msg("test layer");
});
</script>
<body>
<button id="btn5">Test layer</button>
</body>
修饰错误页面
借助登录页面的样式,给错误页面换壳
使错误页面可以显示错误信息,并且提供了返回上一层的按钮。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<%-- 前面与登录页面相同 --%>
<script src="jquery/jquery-3.4.1.js" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
$("button").click(function () {
//用js的方法,完成回退的目的
window.history.back();
});
});
</script>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<%-- 与登录页面同 --%>
</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>
最后此阶段完成时的项目目录结构图如下: