Spring MVC 学习总结(十)——Spring+Spring MVC+MyBatis框架集成(IntelliJ IDEA SSM集成)
与SSH(Struts/Spring/Hibernate/)一样,Spring+SpringMVC+MyBatis也有一个简称SSM,Spring实现业务对象管理,Spring MVC负责请求的转发和视图管理, MyBatis作为数据对象持久化引擎。这样搭配的优点是:轻量、自由度高、Spring与Spring MVC契合度更好。通过一个图书管理示例完成SSM框架的集成,可以将前面学习过的一些内容整合起来,使用到的知识包含:Spring、Spring MVC、MyBatis、JSR303校验、分页、文件上传、路径处理等。
说明:本文只是为了讲解SSM在IDEA中的集成方法,如果您想了解更加详细的内容,请参考本人的另一篇博客内容:Spring MVC 学习总结(六)——Spring+Spring MVC+MyBatis框架集成
一、新建一个基于Maven的Web项目
1.1、创建一个简单的项目,这里不使用模板。如下图所示:
1.2、填写好包名、项目名如下图所示:
1.3、将项目转换成web项目
转换成功:
1.4、项目创建好后添加依赖包,方法有两种,一种直接使用maven,一种手动添加。
使用maven:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.tax</groupId> <artifactId>ssm</artifactId> <version>1.0-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.version>4.3.0.RELEASE</spring.version> </properties> <dependencies> <!--Spring框架核心库 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <!-- Spring MVC --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <!-- aspectJ AOP 织入器 --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency> <!--mybatis-spring适配器 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.0</version> </dependency> <!--Spring java数据库访问包,在本例中主要用于提供数据源 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <!--log4j日志包 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.6.1</version> </dependency> <!-- mybatis ORM框架 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.1</version> </dependency> <!-- JUnit单元测试工具 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> </dependency> <!--c3p0 连接池 --> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <!-- JSTL --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- Servlet核心包 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> <scope>provided</scope> </dependency> <!--JSP --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.1</version> <scope>provided</scope> </dependency> </dependencies> </project>
手动添加:
找到要依赖的所有包
ok后关闭结构
不管使用何总方法,这里的Oracle数据库驱动都要手动添加。
1.5、Idea整合Tomcat
选择项目右上角的“Edit Configurations”
添加tomcat
设置参数
设置应用上下文
添加输出目录
1.6、将依赖包添加到Web-Inf目录下
到这里项目就创建完成了。
二、创建数据库与表
打开Oracle数据库,创建一个表,这里以Book表为例,一个用于存放图书的表,共5个字段id表示编号,title表示图书名称,typename表示图书类型,price表示价格,state表示供出状态。SQL脚本如下:
--图书表 drop table book create table book ( id int not null primary key, --编号 title varchar2(128) not null unique, --书名 typename varchar2(128) not null, --类型 price numeric(10,2) default(0), --价格 state varchar2(10) default('未借出') --状态 ) -- 序列 drop sequence seq_book_id create sequence seq_book_id minvalue 9 maxvalue 9999999999999999999999999999 start with 69 increment by 1 cache 20; --添加图书 insert into book(id,title,Typename,Price,State) select 1,'零基础学Java(全彩版)','计算机',50.60,'未借出' from dual union select 2,'轻量级Java EE企业应用实战','软件工程',85.30,'未借出' from dual union select 3,'Java并发编程的艺术','软件工程',45.40,'未借出' from dual union select 4,'实战Java高并发程序设计','软件开发',48.70,'未借出' from dual union select 5,'Java程序员面试笔试宝典','神话',38.50,'已借出' from dual union select 6,'Java Web从入门到精通','计算机',71.00,'未借出' from dual union select 7,'Java编程思想(第4版)','计算机',70.10,'已借出' from dual union select 8,'深入理解JAVA虚拟机','神话',65.00,'未借出' from dual union select 9,'从零开始写Java Web框架','计算机',63.20,'已借出' from dual --sql select id, title, typename, price, state from book where id=1; commit; select seq_book_id.nextval from dual; insert into book (id, title, typename, price, state) values (seq_book_id.nextval, 'testbook', '计算机', 99.8, '未借出'); update book set title=#{title},typename=#{typename},price=#{price},state=#{state} where id=#{id} delete from book where id=#{id}
表结构如下所示:
测试数据:
三、添加依赖包
项目主要依赖的jar包有Spring核心包、Spring AOP包、Spring MVC包、MyBatis ORM包、MyBatis-Spring适配包、JSTL、JUnit、Log4j2等,具体的pom.xml文件如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.zhangguo</groupId> <artifactId>SSMall</artifactId> <version>0.0.3</version> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.version>4.3.0.RELEASE</spring.version> </properties> <dependencies> <!--Spring框架核心库 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <!-- Spring MVC --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <!-- aspectJ AOP 织入器 --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency> <!--mybatis-spring适配器 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.0</version> </dependency> <!--Spring java数据库访问包,在本例中主要用于提供数据源 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <!--Oracle数据库驱动 --> <dependency> <groupId>Oracle</groupId> <artifactId>Oracle-connector-java</artifactId> <version>5.1.38</version> </dependency> <!--log4j日志包 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.6.1</version> </dependency> <!-- mybatis ORM框架 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.1</version> </dependency> <!-- JUnit单元测试工具 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> </dependency> <!--c3p0 连接池 --> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <!-- JSTL --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- Servlet核心包 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> <scope>provided</scope> </dependency> <!--JSP --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.1</version> <scope>provided</scope> </dependency> <!-- jackson --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.5.2</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.5.2</version> </dependency> <!--JSR303 --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.2.2.Final</version> </dependency> <!--文件上传 --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> <!-- FreeMarker --> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.23</version> </dependency> </dependencies> </project>
如果是第一次依赖相关的包,则需要下载时间,请耐心等待,如果下载失败请手动下载(http://search.maven.org/)后复制到本地的资源库中。依赖后的项目结果如下:
四、新建POJO实体层
为了实现与数据库中的books表进行关系映射新建一个Book图书类,具体代码如下:
package com.tax.model; /**图书 POJO*/ public class Book { private int id; //编号 private String title; //标题 private String typename; //类型 private Double price; //价格 private String state; //状态 //属性 public int getId() { return id; } public void setId(int id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getTypename() { return typename; } public void setTypename(String typename) { this.typename = typename; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } public String getState() { return state; } public void setState(String state) { this.state = state; } }
完成后的界面
五、新建MyBatis SQL映射层
这个项目中我们采用接口与注解结合的形式完成关系与对象间的映射,在接口中定义一些数据访问的方法,接口如下:
package com.tax.dao; import com.tax.model.Book; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; import java.util.List; /**图书数据访问接口*/ public interface IBookDao { /**获得所有图书*/ @Select("select id, title, typename, price, state from book") public List<Book> getAllBooks(); /**获得单个图书对象通过编号*/ @Select("select id, title, typename, price, state from book where id=#{id}") public Book getBookById(int id); /**添加*/ @Insert("insert into Book(id, title, typename, price, state) values(seq_book_id.nextval,#{title}, #{typename}, #{price}, #{state})") public int add(Book entity); /**编辑*/ @Update("update Book set title=#{title}, typename=#{typename}, price=#{price}, state=#{state} where id=#{id}") public int edit(Book entity); /**删除*/ @Delete("delete from Book where id=#{id}") public int delete(int id); }
结果
六、JUnit测试数据访问(非必要)
为了保证数据访问正常,使用JUnit进行单元测试,在另一个源代码目录src/test/java下添加一个名为TestBook的测试用例,编写完成的测试用例如下:
package com.tax.test; import java.util.List; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import com.tax.entities.Book; import com.tax.mapper.BookDAO; import junit.framework.Assert; public class TestBook{ @Test public void getBookPagerTest() { int skip=4; int size=2; SqlSession session=MyBatisUtil.getSession(); try { BookDAO bookdao=session.getMapper(BookDAO.class); List<Book> Book=bookdao.getBookPager(skip, size); Assert.assertEquals(2, Book.size()); } finally { session.close(); } } @Test public void getBookByIdTest() { SqlSession session=MyBatisUtil.getSession(); try { BookDAO bookdao=session.getMapper(BookDAO.class); Book Book=bookdao.getBookById(1); Assert.assertEquals(1, Book.getId()); } finally { session.close(); } } @Test public void getBookCountTest() { SqlSession session=MyBatisUtil.getSession(); try { BookDAO bookdao=session.getMapper(BookDAO.class); Assert.assertEquals(9, bookdao.getBookCount()); } finally { session.close(); } } @Test public void insertTest() { SqlSession session=MyBatisUtil.getSession(); try { Book entity=new Book(); entity.setName("正宗无锡阳山水蜜桃新鲜水果水密桃12个6斤装江浙沪皖顺丰包邮"); entity.setPrice(108); entity.setPicture("nopic.jpg"); BookDAO bookdao=session.getMapper(BookDAO.class); Assert.assertEquals(1, bookdao.insert(entity)); } finally { session.close(); } } @Test public void deleteTest() { SqlSession session=MyBatisUtil.getSession(); try { BookDAO bookdao=session.getMapper(BookDAO.class); Assert.assertEquals(1, bookdao.delete(12)); } finally { session.close(); } } @Test public void update() { SqlSession session=MyBatisUtil.getSession(); try { BookDAO bookdao=session.getMapper(BookDAO.class); Book entity=bookdao.getBookById(12); entity.setName("正宗无锡阳山水蜜桃新鲜水果水密桃12个6斤装"); entity.setPrice(107); entity.setPicture("nopicture.jpg"); Assert.assertEquals(1, bookdao.update(entity)); } finally { session.close(); } } }
MyBatis访问数据库的工具类如下:
package com.tax.test; import java.io.InputStream; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; public abstract class MyBatisUtil { public static SqlSessionFactory getSqlSessionFactory(){ // 获得环境配置文件流 InputStream config = MyBatisUtil.class.getClassLoader().getResourceAsStream("MyBatisCfg.xml"); // 创建sql会话工厂 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(config); return factory; } //获得会话 public static SqlSession getSession(){ return getSqlSessionFactory().openSession(true); } /** * 获得得sql会话 * @param isAutoCommit 是否自动提交,如果为false则需要sqlSession.commit();rollback(); * @return sql会话 */ public static SqlSession getSession(boolean isAutoCommit){ return getSqlSessionFactory().openSession(isAutoCommit); } }
MyBatis配置文件MyBatisCfg.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> <properties resource="db.properties"></properties> <typeAliases> <package name="com.tax.entities" /> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <property name="driver" value="${Oracle.driver}" /> <property name="url" value="${Oracle.url}" /> <property name="username" value="${Oracle.uid}" /> <property name="password" value="${Oracle.password}" /> </dataSource> </environment> </environments> <mappers> <mapper resource="com/zhangguo/ssmall/mapper/BookMapper.xml" /> </mappers> </configuration>
配置文件中使用到了db.properties属性文件,该文件用于存放数据库连接信息,文件内容如下:
#Oracle Oracle.driver=com.Oracle.jdbc.Driver Oracle.url=jdbc:Oracle://localhost:3306/db1 Oracle.uid=root Oracle.password=root
运行测试,一切正常,测试结果如下:
这里需要注意的是MyBatis配置文件的内容在后面与Spring整合后是会变化的,使用JUnit测试并未使用到Spring框架。
七、完成Spring整合MyBatis配置
7.1、在源代码的根目录下修改db.properties文件,用于存放数据库连接信息,文件内容如下:
#Oracle Oracle.driver=oracle.jdbc.driver.OracleDriver Oracle.url=jdbc:oracle:thin:@127.0.0.1:1521:orcl Oracle.uid=tax Oracle.password=orcl Oracle.acquireIncrement=5 Oracle.initialPoolSize=10 Oracle.minPoolSize=5 Oracle.maxPoolSize=20
7.2、在源代码的resource目录下新建 applicationContext.xml文件,用于整合MyBatis与Spring,非常关键,具体的内容如下:
applicationContext.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:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd"> <!--1 引入属性文件,在配置中占位使用 --> <context:property-placeholder location="classpath*:db.properties" /> <!--2 配置C3P0数据源 --> <bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <!--驱动类名 --> <property name="driverClass" value="${Oracle.driver}" /> <!-- url --> <property name="jdbcUrl" value="${Oracle.url}" /> <!-- 用户名 --> <property name="user" value="${Oracle.uid}" /> <!-- 密码 --> <property name="password" value="${Oracle.password}" /> <!-- 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数 --> <property name="acquireIncrement" value="${Oracle.acquireIncrement}"></property> <!-- 初始连接池大小 --> <property name="initialPoolSize" value="${Oracle.initialPoolSize}"></property> <!-- 连接池中连接最小个数 --> <property name="minPoolSize" value="${Oracle.minPoolSize}"></property> <!-- 连接池中连接最大个数 --> <property name="maxPoolSize" value="${Oracle.maxPoolSize}"></property> </bean> <!--3 会话工厂bean sqlSessionFactoryBean --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 配置文件路径 --> <property name="configLocation" value="classpath:MyBatisCfg.xml"></property> <!-- 数据源 --> <property name="dataSource" ref="datasource"></property> <!-- sql映射文件路径 --> <!--<property name="mapperLocations" value="classpath*:com/tax/dao/*Mapper.xml"></property>--> </bean> <!--4 自动扫描对象关系映射 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--指定会话工厂,如果当前上下文中只定义了一个则该属性可省去 --> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> <!-- 指定要自动扫描接口的基础包,实现接口 --> <property name="basePackage" value="com.tax.dao"></property> </bean> <!--5 声明式事务管理 --> <!--定义事物管理器,由spring管理事务 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="datasource"></property> </bean> <!--支持注解驱动的事务管理,指定事务管理器 --> <tx:annotation-driven transaction-manager="transactionManager" /> <!--6 容器自动扫描IOC组件 --> <context:component-scan base-package="com.tax"></context:component-scan> <!--7 aspectj支持自动代理实现AOP功能 --> <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy> </beans>
从配置文件中可以看出第3点会话工厂配置中指定了MyBatis配置文件的位置与名称,其实也可以省去,在这里可以通过属性配置好。但个人认为当多个框架整合在一起时最后将配置文件分开,便于修改。修改后的MyBatisCfg.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> <properties resource="db.properties"></properties> <settings> <!--指定mybatis使用日志组件 --> <setting name="logImpl" value="LOG4J2" /> <!--开启全局的二级缓存 --> <setting name="cacheEnabled" value="false" /> <!--开启延时加载,如果有关联关系,则默认不会获取数据 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。 在association中指定fetchType="eager(立即)" 或者 lazy(延迟) 默认:false --> <setting name="lazyLoadingEnabled" value="true" /> <!--true时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载; false,每种属性将会按需加载。 默认为:true--> <setting name="aggressiveLazyLoading" value="false" /> </settings> <typeAliases> <package name="com.tax.model" /> </typeAliases> </configuration>
中间有一大段注释了,是因为MyBatis-Spring适配器已完成了这部分内容的工作,注释不删除的原因是因为JUnit测试时还要使用,其它也可以使用两个不同的文件。
八、配置web.xml加载Spring容器与MVC
修改web.xml文件,注册加载Spring容器所需的监听器;注册Spring MVC前置控制器Servlet,中间还设置了Servlet3.0上传所需的参数;添加了一个全局的编码过滤器。
<?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_3_1.xsd" version="3.1"> <listener> <description>Spring容器加载监听器</description> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <description>设置Spring加载时的配置文件位置,默认位置在WEB-INF/lib目录下</description> <param-name>contextConfigLocation</param-name> <param-value>classpath*:applicationContext.xml</param-value> </context-param> <!--Spring MVC 前置Servlet,中心控制器 --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <!--Spring MVC配置文件路径 --> <param-value>classpath*:springmvc-servlet.xml</param-value> </init-param> <!-- 启动动优先级,越小越早加载 --> <load-on-startup>1</load-on-startup> <!--Servlet3.0以上文件上传配置 --> <multipart-config> <!--上传文件的最大限制5MB --> <max-file-size>5242880</max-file-size> <!--请求的最大限制20MB --> <max-request-size>20971520</max-request-size> <!--当文件的大小超过临界值时将写入磁盘 --> <file-size-threshold>0</file-size-threshold> </multipart-config> </servlet> <!-- Servlet访问的路径映射,所有的访问都必须经过调度用的前置控制品 --> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!--编码过滤器 --> <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>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <!-- 路径映射 --> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
在src/main/java源代码目录下添加applicationContext.xml文件,用于配置Spring,内容在上一节中已列出。
在src/main/java源代码目录下添加Spring MVC配置文件springmvc-servlet.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-4.3.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd"> <!-- 自动扫描包,实现支持注解的IOC --> <context:component-scan base-package="com.tax" /> <!-- Spring MVC不处理静态资源 --> <mvc:default-servlet-handler /> <!-- 支持mvc注解驱动 --> <mvc:annotation-driven enable-matrix-variables="true" /> <!-- 配置映射媒体类型的策略 --> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"> <property name="removeSemicolonContent" value="false" /> </bean> <!-- 内部视图解析器,JSP与JSTL模板 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver"> <!--指定视图渲染类 --> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <!--自动添加到路径中的前缀 --> <property name="prefix" value="/WEB-INF/views/" /> <!--自动添加到路径中的后缀 --> <property name="suffix" value=".jsp" /> <!--设置所有视图的内容类型,如果视图本身设置内容类型视图类可以忽略 --> <property name="contentType" value="text/html;charset=UTF-8" /> <!-- 优先级,越小越前 --> <property name="order" value="1" /> </bean> <!--文件上传解析器 --> <!--Spring MVC默认不能识别multipart格式的文件内容 --> <bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"> </bean> </beans>
九、创建服务层
在包com.tax.service下添加IBookService.java文件,该文件是一个服务接口,内容如下:
package com.tax.service; import com.tax.model.Book; import java.util.List; /**图书业务接口*/ public interface IBookService { /**获得所有图书*/ public List<Book> getAllBooks(); /**获得单个图书对象通过编号*/ public Book getBookById(int id); /**添加*/ public int add(Book entity); /**编辑*/ public int edit(Book entity); /**删除*/ public int delete(int id); }
在包com.tax.service下添加类BookService.java,实现接口BookService,用于完成图书业务逻辑,由于是示例代码所以比较空;中间使用了两个注解一个是@Service,用于提供给需要服务的类自动装配,当Spring IOC容器启动时被扫描到该类型会自动添加实例到Spring容器中;另一个注解是@Resource用于完成自动装配功能,在Spring容器中找到BookDAO类型的对象,代码如下:
package com.tax.service; import com.tax.dao.IBookDao; import com.tax.model.Book; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; /**图书业务*/ @Service public class BookService implements IBookService { @Autowired IBookDao bookDao; public List<Book> getAllBooks() { return bookDao.getAllBooks(); } public Book getBookById(int id) { return bookDao.getBookById(id); } public int add(Book entity) { return bookDao.add(entity); } public int edit(Book entity) { return bookDao.edit(entity); } public int delete(int id) { return bookDao.delete(id); } }
十、完成图书管理功能
10.1、图书列表
定义BookController控制器,映射访问路径,需要使用到的图书服务使用自动装配完成,代码如下:
package com.tax.controller; import com.tax.service.IBookService; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import javax.annotation.Resource; /**图书控制器*/ @Controller @RequestMapping("/Book") public class BookController { @Resource IBookService bookService; @RequestMapping("/list") public String list(Model model){ //获得所有的图书带到页面中 model.addAttribute("books",bookService.getAllBooks()); return "book/list"; } }
在views/jstl/Book目录下添加视图list.jsp页面,页面的内容如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>图书管理</title> <meta charset="UTF-8"/> </head> <body> <h2>图书管理</h2> <table width="100%" id="tab" border="1"> <tr> <td>编号</td> <td>书名</td> <td>类型</td> <td>价格</td> <td>状态</td> <td>操作</td> </tr> <c:forEach var="book" items="${books}"> <tr> <td>${book.id}</td> <td>${book.title}</td> <td>${book.typename}</td> <td>${book.price}</td> <td>${book.state}</td> <td><a href="#">删除</a> | <a href="#">编辑</a></td> </tr> </c:forEach> </table> </body> </html>
测试运行结果:
11.2、删除与多删除功能
修改业务层接口与实现:
IBookService:
package com.tax.service; import com.tax.model.Book; import java.util.List; /**图书业务接口*/ public interface IBookService { /**获得所有图书*/ public List<Book> getAllBooks(); /**获得单个图书对象通过编号*/ public Book getBookById(int id); /**添加*/ public int add(Book entity); /**编辑*/ public int edit(Book entity); /**删除*/ public int delete(int id); /**多删除*/ public int delete(int[] ids); }
BookService:
package com.tax.service; import com.tax.dao.IBookDao; import com.tax.model.Book; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; /**图书业务*/ @Service public class BookService implements IBookService { @Autowired IBookDao bookDao; public List<Book> getAllBooks() { return bookDao.getAllBooks(); } public Book getBookById(int id) { return bookDao.getBookById(id); } public int add(Book entity) { return bookDao.add(entity); } public int edit(Book entity) { return bookDao.edit(entity); } public int delete(int id) { return bookDao.delete(id); } //多删除,重载 public int delete(int[] ids) { int rows=0; for (Integer id:ids) { //遍历所有的编号 rows+=delete(id); //调用单删除功能,累计删除行数 } return rows; } }
为了实现删除与多删除功能,修改控制器,增加2个action,delete请求处理方法用于删除单个记录,id是路径变量指定要删除的图书编号;pageNO是请求参数,保持状态的目的是为了删除后让页面继续停留在某一页,不过这里有问题的是当某一页的内容只有一条记录里就需要重新计算了;rediredtAttributes是为了保持重定向后的message值。
package com.tax.controller; import com.tax.service.IBookService; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import javax.annotation.Resource; /** * 图书控制器 */ @Controller @RequestMapping("/book") public class BookController { @Resource IBookService bookService; @RequestMapping("/list") public String list(Model model) { //获得所有的图书带到页面中 model.addAttribute("books", bookService.getAllBooks()); return "book/list"; //WEB-INF/views/book/list.jsp } //http://localhost:8080/ssm/book/del/5 @RequestMapping("/del/{id}") public String del(Model model, @PathVariable int id, RedirectAttributes flash) { //执行删除,如果影响行行数大于0,则成功 if (bookService.delete(id) > 0) { flash.addFlashAttribute("msg","删除成功!"); } else { flash.addFlashAttribute("msg","删除失败!"); } //重定向 return "redirect:/book/list"; } @RequestMapping("/dels") public String dels(Model model, @RequestParam(value = "id",required = false) int[] ids, RedirectAttributes flash) { //执行删除,如果影响行行数大于0,则成功 int rows=bookService.delete(ids); if ( rows> 0) { flash.addFlashAttribute("msg","删除成功"+rows+"行!"); } else { flash.addFlashAttribute("msg","删除失败!"); } //重定向 return "redirect:/book/list"; } }
为了配合删除,修改list.jsp页面,修改后的list.jsp页面如下所示:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>图书管理</title> <meta charset="UTF-8"/> </head> <body> <h2>图书管理</h2> <form action="dels" method="post"> <table width="100%" id="tab" border="1"> <tr> <td><input type="checkbox" id="chbAll" /></td> <td>编号</td> <td>书名</td> <td>类型</td> <td>价格</td> <td>状态</td> <td>操作</td> </tr> <c:forEach var="book" items="${books}"> <tr> <td><input name="id" type="checkbox" value="${book.id}"/></td> <td>${book.id}</td> <td>${book.title}</td> <td>${book.typename}</td> <td>${book.price}</td> <td>${book.state}</td> <td><a href="del/${book.id}" class="del">删除</a> | <a href="#">编辑</a></td> </tr> </c:forEach> </table> <p> <input type="submit" value="删除选择项" class="del"/> </p> </form> <p> ${msg} </p> <script src="<c:url value="/js/jquery-1.11.3.min.js"></c:url>"></script> <script> $(".del").click(function(){ return confirm("您确定要删除吗?"); }); //全选与反选 $("#chbAll").change(function(){ $("input[name=id]").prop("checked",$(this).prop("checked")); }); var msg='${msg}'; if(msg){ alert(msg); } </script> </body> </html>
运行结果如下所示:
基中的多删除功能可以改进为一次性让数据库删除完成。
11.3、新增图书功能
在控制器中添加2个action,一个是add用于完成添加页面展示,一个是addSave用于完成添加保存处理,代码如下:
//添加 @RequestMapping("/add") public String add(Model model) { return "book/add"; } //添加保存 @RequestMapping("/addSave") public String add(Model model, Book book, RedirectAttributes flash) { //执行删除,如果影响行行数大于0,则成功 if (bookService.add(book) > 0) { flash.addFlashAttribute("msg","添加成功!"); //重定向 return "redirect:/book/list"; } else { flash.addFlashAttribute("msg","添加失败!"); flash.addFlashAttribute("",book); return "redirect:/book/add"; } }
在views/jstl/book目录下新增加add.jsp页面,页面内容如下:
<%-- Created by IntelliJ IDEA. User: Administrator Date: 2018/3/20 Time: 10:31 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>添加图书</title> <meta charset="UTF-8"/> </head> <body> <h2>添加图书</h2> <form action="addSave" method="post"> <fieldset> <legend>图书详细</legend> <p> <label for="title">书名:</label> <input type="text" name="title" id="title" value="${book.title}"/> </p> <p> <label for="typename">类型:</label> <select name="typename" id="typename"> <option value="经济">经济</option> <option value="文学">文学</option> <option value="天文">天文</option> </select> </p> <p> <label for="price">价格:</label> <input type="text" name="price" id="price" value="${book.price}"/> </p> <p> <label for="state2">状态:</label> <input type="radio" name="state" id="state1" value="已借出"/>已借出 <input type="radio" name="state" id="state2" value="未借出"/>未借出 </p> <p> <input type="submit" value="提交"/> </p> </fieldset> </form> </body> </html>
首页添加新增按钮
<p> <input type="submit" value="删除选择项" class="del"/> <input type="button" value="添加" onclick="location.href='<c:url value="/book/add"></c:url>'" /> </p>
运行结果:
执行添加
11.4、编辑图书功能
与新增加类似,在控制器下新增两个action,一个用于展示编辑,有一个用于执行编辑后保存,代码如下所示:
//编辑 @RequestMapping("/edit") public String edit(Model model,int id) { //将要编辑的图书对象带入视图 model.addAttribute("book",bookService.getBookById(id)); return "book/edit"; } //编辑保存 @RequestMapping("/editSave") public String edit(Model model, Book book, RedirectAttributes flash) { //执行更新,如果影响行行数大于0,则成功 if (bookService.edit(book) > 0) { flash.addFlashAttribute("msg","更新成功!"); //重定向 return "redirect:/book/list"; } else { flash.addFlashAttribute("msg","更新失败!"); flash.addFlashAttribute("book",book); return "redirect:/book/edit"; } }
在views/jstl/book目录下新增加edit.jsp页面,页面内容如下:
<%-- Created by IntelliJ IDEA. User: Administrator Date: 2018/3/20 Time: 10:31 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>编辑图书</title> <meta charset="UTF-8"/> </head> <body> <h2>编辑图书</h2> <form action="editSave" method="post"> <fieldset> <legend>图书详细</legend> <p> <label for="title">书名:</label> <input type="text" name="title" id="title" value="${book.title}"/> </p> <p> <label for="typename">类型:</label> <select name="typename" id="typename"> <option value="计算机" ${book.typename=="计算机"?"selected='selected'":""}>计算机</option> <option value="软件工程" ${book.typename=="软件工程"?"selected='selected'":""}>软件工程</option> <option value="神话" ${book.typename=="神话"?"selected='selected'":""}>神话</option> <option value="软件开发" ${book.typename=="软件开发"?"selected='selected'":""}>软件开发</option> </select> </p> <p> <label for="price">价格:</label> <input type="text" name="price" id="price" value="${book.price}"/> </p> <p> <label for="state2">状态:</label> <input type="radio" name="state" id="state1" value="已借出" ${book.state=="已借出"?"checked='checked'":""}/>已借出 <input type="radio" name="state" id="state2" value="未借出" ${book.state=="未借出"?"checked='checked'":""}/>未借出 </p> <p> <input type="hidden" name="id" value="${book.id}" /> <input type="submit" value="保存"/> </p> </fieldset> </form> </body> </html>
首页编辑按钮:
<td><a href="del/${book.id}" class="del">删除</a> | <a href="<c:url value="/book/edit?id=${book.id}"></c:url>">编辑</a></td>
运行结果:
11.5、日志、首页、样式与最终的控制器
为了将MyBatis与Hibernate Validation的日志信息展示在控制中,需要添加log4j2的引用,这部分内容在pom.xml中已配置完成了,另外在项目的根目录下需要添加一个log4j2的配置文件log4j2.xml,内容如下:
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="off" monitorInterval="1800"> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" /> </Console> </Appenders> <Loggers> <Root level="debug"> <AppenderRef ref="Console" /> </Root> </Loggers> </Configuration>
在webapp目录下添加index.jsp,首页是这个程序的入口,只完成了转发功能,页面内容如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <jsp:forward page="Book/list"></jsp:forward>
所有页面基本都引用了同一个样式表styles/main.css文件,文件内容如下:
@CHARSET "UTF-8"; * { margin: 0; padding: 0; font-family: microsoft yahei; font-size: 14px; } body { padding-top: 20px; } .main { width: 90%; margin: 0 auto; border: 1px solid #777; padding: 20px; border-radius: 5px; } .main .title { font-size: 20px; font-weight: normal; border-bottom: 1px solid #ccc; margin-bottom: 15px; padding-bottom: 5px; color: #006ac1; } .main .title span { display: inline-block; font-size: 20px; color: #fff; padding: 0 8px; background: orangered; border-radius: 5px; } a { color: #006ac1; text-decoration: none; } a:hover { color: orangered; } .tab td, .tab, .tab th { border: 1px solid #777; border-collapse: collapse; } .tab td, .tab th { line-height: 26px; height: 26px; padding-left: 5px; } .abtn { display: inline-block; height: 18px; line-height: 18px; background: #006ac1; color: #fff; padding: 0 5px; border-radius: 5px; } .btn { height: 18px; line-height: 18px; background: #006ac1; color: #fff; padding: 0 8px; border: 0; border-radius: 5px; } .abtn:hover, .btn:hover { background: orangered; color: #fff; } p { padding: 5px 0; } fieldset { border: 1px solid #ccc; padding: 5px 10px; } fieldset legend { margin-left: 10px; font-size: 16px; } a.out,input.out { height: 23px; line-height: 23px; } form{ margin:10px 0; } .error { color:red }
最终的控制器BookController.java文件内容如下:
package com.tax.controller; import com.tax.model.Book; import com.tax.service.IBookService; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import javax.annotation.Resource; /** * 图书控制器 */ @Controller @RequestMapping("/book") public class BookController { @Resource IBookService bookService; @RequestMapping("/list") public String list(Model model) { //获得所有的图书带到页面中 model.addAttribute("books", bookService.getAllBooks()); return "book/list"; //WEB-INF/views/book/list.jsp } //单删除 @RequestMapping("/del/{id}") public String del(Model model, @PathVariable int id, RedirectAttributes flash) { //执行删除,如果影响行行数大于0,则成功 if (bookService.delete(id) > 0) { flash.addFlashAttribute("msg","删除成功!"); } else { flash.addFlashAttribute("msg","删除失败!"); } //重定向 return "redirect:/book/list"; } //多删除 @RequestMapping("/dels") public String dels(Model model, @RequestParam(value = "id",required = false) int[] ids, RedirectAttributes flash) { //执行删除,如果影响行行数大于0,则成功 int rows=bookService.delete(ids); if ( rows> 0) { flash.addFlashAttribute("msg","删除成功"+rows+"行!"); } else { flash.addFlashAttribute("msg","删除失败!"); } //重定向 return "redirect:/book/list"; } //添加 @RequestMapping("/add") public String add(Model model) { return "book/add"; } //添加保存 @RequestMapping("/addSave") public String add(Model model, Book book, RedirectAttributes flash) { //执行保存,如果影响行行数大于0,则成功 if (bookService.add(book) > 0) { flash.addFlashAttribute("msg","添加成功!"); //重定向 return "redirect:/book/list"; } else { flash.addFlashAttribute("msg","添加失败!"); flash.addFlashAttribute("book",book); return "redirect:/book/add"; } } //编辑 @RequestMapping("/edit") public String edit(Model model,int id) { //将要编辑的图书对象带入视图 model.addAttribute("book",bookService.getBookById(id)); return "book/edit"; } //编辑保存 @RequestMapping("/editSave") public String edit(Model model, Book book, RedirectAttributes flash) { //执行更新,如果影响行行数大于0,则成功 if (bookService.edit(book) > 0) { flash.addFlashAttribute("msg","更新成功!"); //重定向 return "redirect:/book/list"; } else { flash.addFlashAttribute("msg","更新失败!"); flash.addFlashAttribute("book",book); return "redirect:/book/edit"; } } }
整合H-UI后的效果
十二、总结与问题
通个该示例将前面几章的内容整合起来,巩固了前几章的内容;示例中还可以尝试使用FreeMarker视图;示例中没有前端验证都是后台验证,可以使用jQuery扩展插件Validate实现前端校验;有些功能可以结合AJAX完成更加合理;路径是要非常小心的,后台重定向时,前台提交表单的路径,可以使用base标签和c:url。内容比较简单,适合初学,只是希望能起到抛砖引玉、以小见大的作用,谢谢阅读!
在Idea的spring工程里,经常会遇到Could not autowire. No beans of 'xxxx' type found的错误提示。但程序的编译和运行都是没有问题的,这个错误提示并不会产生影响。
降低Autowired检测的级别,将Severity的级别由之前的error改成warning或其它可以忽略的级别。