為了梳理前面學習的《Spring整合MyBatis(Maven+MySQL)一》與《Spring整合MyBatis(Maven+MySQL)二》中的內容,准備做一個完整的示例完成一個簡單的圖書管理功能,主要使用到的技術包含Spring、MyBatis、Maven與MySQL等。最後的運行效果如下:
項目結構如下:
一、新建一個基於Maven的Web項目
1.1、創建一個簡單的Maven項目,項目信息如下:
1.2、修改層面信息,在項目上右鍵選擇屬性,再選擇“Project Facets”,先設置java運行環境為1.7,先去掉"Dynamic Web Module"前的勾,然後保存關閉;再打開勾選上"Dynamic Web Module",版本選擇“3.0”;這裡在左下解會出現一個超鏈接,創建“Web Content”,完成關閉。
1.3、修改項目的部署內容。項目上右鍵屬性,選擇“Deplyment Assembly”,刪除不需要發布的內容如:帶“test”的兩個目錄,WebContent目錄,再添加一個main下的webapp目錄。
修改後的結果如下所示:
1.4、修改項目內容。將WebContent下的內容復制到/src/main/webapp下,再刪除WebContent目錄,修改後的結果如下所示:
1.5、添加“服務器運行時(Server Runtime)”,添加後的結果如下:
二、創建數據庫與表
啟動MySQL,創建數據庫,新建表books,插入測試數據,完成後的表如下所示:
創建表的sql腳本如下:
/* Navicat MySQL Data Transfer Source Server : localhost Source Server Version : 50536 Source Host : localhost:3306 Source Database : db1 Target Server Type : MYSQL Target Server Version : 50536 File Encoding : 65001 Date: 2016-07-06 22:05:07 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for `books` -- ---------------------------- DROP TABLE IF EXISTS `books`; CREATE TABLE `books` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '編號', `title` varchar(100) NOT NULL COMMENT '書名', `price` decimal(10,2) DEFAULT NULL COMMENT '價格', `publishDate` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '出版日期', PRIMARY KEY (`id`), UNIQUE KEY `title` (`title`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of books -- ---------------------------- INSERT INTO `books` VALUES ('1', 'Java編程思想', '98.50', '2005-01-02 00:00:00'); INSERT INTO `books` VALUES ('2', 'HeadFirst 設計模式', '55.70', '2010-11-09 00:00:00'); INSERT INTO `books` VALUES ('3', '第一行代碼Android', '69.90', '2015-06-23 00:00:00'); INSERT INTO `books` VALUES ('4', 'C++編程思想', '88.50', '2004-01-09 00:00:00'); INSERT INTO `books` VALUES ('5', 'HeadFirst Java', '55.70', '2013-12-17 00:00:00'); INSERT INTO `books` VALUES ('6', '瘋狂Android', '19.50', '2014-07-31 00:00:00');
需特別注意的是書名是唯一鍵。
三、添加依賴
項目主要依賴的jar包有Spring核心包、Spring AOP包、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>BookStore</artifactId> <version>0.0.1</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> <!-- aspectJ AOP 織入器 --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency> <!-- Spring Web --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</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> <!--mysql數據庫驅動 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-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> </dependencies> </project>
如果是第一次依賴相關的包,則需要下載時間,請耐心等待,如果下載失敗請手動下載後復制到本地的資源庫中。依賴後的項目結果如下:
四、新建POJO實體層
為了實現與數據庫中的books表進行關系映射新建一個Book類,具體代碼如下:
package com.zhangguo.bookstore.entities; import java.util.Date; /** * 圖書實體 */ public class Book { /** * 編號 */ private int id; /** * 書名 */ private String title; /** * 價格 */ private double price; /** * 出版日期 */ private Date publishDate; public Book(int id, String title, double price, Date publishDate) { this.id = id; this.title = title; this.price = price; this.publishDate = publishDate; } public Book() { } 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 double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public Date getPublishDate() { return publishDate; } public void setPublishDate(Date publishDate) { this.publishDate = publishDate; } }
五、新建MyBatis SQL映射層
這個項目中我們采用接口與xml結束的形式完成關系與對象間的映射,在接口中定義一些數據訪問的方法,在xml文件中定義實現數據訪問需要的sql腳本。圖書數據訪問映射接口如下:
package com.zhangguo.bookstore.mapper; import java.util.List; import org.apache.ibatis.annotations.Param; import com.zhangguo.bookstore.entities.Book; /** * 圖書數據訪問接口 */ public interface BookDAO { /** * 獲得所有圖書 */ public List<Book> getAllBooks(); /** * 根據圖書編號獲得圖書對象 */ public Book getBookById(@Param("id") int id); /** * 添加圖書 */ public int add(Book entity); /** * 根據圖書編號刪除圖書 */ public int delete(int id); /** * 更新圖書 */ public int update(Book entity); }
為MyBatis ORM創建的映射文件BookMapper.xml(命名盡量都遵循一個規則,便於掃描,這裡約定以實體名+Mapper)如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--命名空間應該是對應接口的包名+接口名 --> <mapper namespace="com.zhangguo.bookstore.mapper.BookDAO"> <!--id應該是接口中的方法,結果類型如沒有配置別名則應該使用全名稱 --> <!--獲得所有圖書 --> <select id="getAllBooks" resultType="Book"> select id,title,price,publishDate from books </select> <!--獲得圖書對象通過編號 --> <select id="getBookById" resultType="Book"> select id,title,price,publishDate from books where id=#{id} </select> <!-- 增加 --> <insert id="add"> insert into books(title,price,publishDate) values(#{title},#{price},#{publishDate}) </insert> <!-- 刪除 --> <delete id="delete"> delete from books where id=#{id} </delete> <!-- 更新 --> <update id="update"> update books set title=#{title},price=#{price},publishDate=#{publishDate} where id=#{id} </update> </mapper>
六、完成Spring整合MyBatis配置
6.1、在源代碼的根目錄下新建 db.properties文件,用於存放數據庫連接信息,文件內容如下:
#mysql jdbc jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=UTF-8 jdbc.uid=root jdbc.pwd=root
6.2、在源代碼的根目錄下新建 applicationContext.xml文件,用於整合MyBatis與Spring,該文件是整個項目的控制中心,非常關鍵,具體的內容如下:
<?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.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="${jdbc.driver}" /> <!-- url --> <property name="jdbcUrl" value="${jdbc.url}" /> <!-- 用戶名 --> <property name="user" value="${jdbc.uid}" /> <!-- 密碼 --> <property name="password" value="${jdbc.pwd}" /> <!-- 當連接池中的連接耗盡的時候c3p0一次同時獲取的連接數 --> <property name="acquireIncrement" value="5"></property> <!-- 初始連接池大小 --> <property name="initialPoolSize" value="10"></property> <!-- 連接池中連接最小個數 --> <property name="minPoolSize" value="5"></property> <!-- 連接池中連接最大個數 --> <property name="maxPoolSize" value="20"></property> </bean> <!--3 會話工廠bean sqlSessionFactoryBean --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 數據源 --> <property name="dataSource" ref="datasource"></property> <!-- 別名 --> <property name="typeAliasesPackage" value="com.zhangguo.bookstore.entities"></property> <!-- sql映射文件路徑 --> <property name="mapperLocations" value="classpath*:com/zhangguo/bookstore/mapper/*Mapper.xml"></property> </bean> <!--4 自動掃描對象關系映射 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--指定會話工廠,如果當前上下文中只定義了一個則該屬性可省去 --> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> <!-- 指定要自動掃描接口的基礎包,實現接口 --> <property name="basePackage" value="com.zhangguo.bookstore.mapper"></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.zhangguo.bookstore"></context:component-scan> <!--7 aspectj支持自動代理實現AOP功能 --> <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy> </beans>
共有7處配置,第7處配置非必要,另外關於事務管理可以選擇AOP攔截式事務管理。
七、創建服務層
創建BookService服務類,完成圖書管理業務,有些項目中也叫業務層,這裡我們叫服務層,具體實現如下:
package com.zhangguo.bookstore.service; import java.util.List; import javax.annotation.Resource; import org.apache.ibatis.annotations.Param; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.zhangguo.bookstore.entities.Book; import com.zhangguo.bookstore.mapper.BookDAO; @Service public class BookService{ @Resource BookDAO bookdao; public List<Book> getAllBooks() { return bookdao.getAllBooks(); } public Book getBookById(int id){ return bookdao.getBookById(id); } public int add(Book entity) throws Exception { if(entity.getTitle()==null||entity.getTitle().equals("")){ throw new Exception("書名必須不為空"); } return bookdao.add(entity); } @Transactional public int add(Book entity1,Book entityBak){ int rows=0; rows=bookdao.add(entity1); rows=bookdao.add(entityBak); return rows; } public int delete(int id) { return bookdao.delete(id); } /** * 多刪除 */ public int delete(String[] ids){ int rows=0; for (String idStr : ids) { int id=Integer.parseInt(idStr); rows+=delete(id); } return rows; } public int update(Book entity) { return bookdao.update(entity); } }
服務層不只是一個dao的接力棒,認為他可有可無,其實是因為我們現在的的示例中沒有涉及到更多的復雜業務,所以顯得比較空,實現開發可能有更多的業務邏輯要在這裡處理。另外給bookdao成員變量注解為自動裝配,service類注解為IOC組件。
八、JUnit單元測試服務類
為了確保服務類中的每個方法正確,先使用JUnit進行單元測試,測試代碼如下:
package com.zhangguo.bookstore.test; import static org.junit.Assert.*; import java.util.Date; import java.util.List; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.zhangguo.bookstore.entities.Book; import com.zhangguo.bookstore.service.BookService; public class TestBookService { static BookService bookservice; @BeforeClass public static void before(){ ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml"); bookservice=ctx.getBean(BookService.class); } @Test public void testGetAllBooks() { List<Book> books=bookservice.getAllBooks(); assertNotNull(books); } @Test public void testAdd() { Book entity=new Book(0, "Hibernate 第七版", 78.1, new Date()); try { assertEquals(1, bookservice.add(entity)); } catch (Exception e) { e.printStackTrace(); } } @Test public void testDeleteInt() { assertEquals(1, bookservice.delete(9)); } @Test public void testDeleteStringArray() { String[] ids={"7","11","12"}; assertEquals(3, bookservice.delete(ids)); } @Test public void testUpdate() { Book entity=new Book(7, "Hibernate 第二版", 79.1, new Date()); try { assertEquals(1, bookservice.update(entity)); } catch (Exception e) { e.printStackTrace(); } } @Test public void testGetBookById() { assertNotNull(bookservice.getBookById(1)); } @Test public void testAddDouble(){ //因為書名相同,添加第二本會失敗,用於測試事務 Book entity1=new Book(0, "Hibernate 第八版", 78.1, new Date()); Book entity2=new Book(0, "Hibernate 第八版", 78.1, new Date()); assertEquals(2, bookservice.add(entity1, entity2)); } }
所有的測試均通過,有一個想法就是能否測試完成後數據庫還原,如刪除的數據在測試後不被真正刪除。
九:加載Spring容器與獲得容器對象
9.1、修改web.xml文件,添加加載Spring容器用的監聽器,修改後的結果如下:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0" > <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <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> </web-app>
類org.springframework.web.context.ContextLoaderListener處在Spring-web.jar包中,要記得在pom.xml中添加依賴,測試是否加載成功的簡單辦法是:重新啟動tomcat查看控制信息。
9.2、為了方便獲得Spring容器實例,定義一個CtxUtil工具類,工具類的代碼如下:
package com.zhangguo.bookstore.action; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** * Spring容器上下文工具類,用於獲取當前的Spring容器 * 實現了接口ApplicationContextAware且該類被Spring管理 *則會自動調用setApplicationContext方法獲取Spring容器對象 */ @Component public class CtxUtil implements ApplicationContextAware { public static ApplicationContext ctx; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ctx=applicationContext; } /** * 根據類型獲得bean */ public static <T> T getBean(Class<T> clazz){ return ctx.getBean(clazz); } /** * 根據名稱名稱獲得bean */ public static Object getBean(String name){ return ctx.getBean(name); } }
十、簡單MVC控制器封裝
為了實現一個簡單的MVC基礎控制器,定義了一個叫BaseController的Servlet,可以讓其它的Servlet繼承該Servlet獲得部分MVC功能,具體代碼如下:
package com.zhangguo.bookstore.action; import java.io.IOException; import java.lang.reflect.*; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet基類 * 自定義控制器基類 */ public class BaseController extends HttpServlet { private static final long serialVersionUID = 1L; protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=utf-8"); // 獲得要執行的方法名 String act = request.getParameter("act"); // 如果用戶沒有提供方法名 if (act == null || act.equals("")) { // 默認方法 act = "execute"; } // 根據方法名獲得方法信息獲得方法信息 Method method; try { // 在對象中獲得類型信息,在類型中獲得方法通過方法名,與參數類型 method = this.getClass().getMethod(act, HttpServletRequest.class, HttpServletResponse.class); // 調用方法,在當前對象中調用,傳遞參數request與response,獲得返回結果 String targetUri = method.invoke(this, request, response) + ""; // 如果返回的url是以redirect開始,則是重定向 if (targetUri.startsWith("redirect:")) { response.sendRedirect(targetUri.substring(9, targetUri.length())); } else { // 轉發 request.getRequestDispatcher(targetUri).forward(request, response); } } catch (Exception e) { response.sendError(400, e.getMessage()); e.printStackTrace(); } } public void execute(HttpServletRequest request, HttpServletResponse response) throws IOException { response.sendError(400, "請使用參數act指定您要訪問的方法"); } }
十一、完成圖書管理功能
11.1、定義BookController控制器,繼承BaseController,代碼如下:
package com.zhangguo.bookstore.action; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.zhangguo.bookstore.service.BookService; @WebServlet("/BookController.do") public class BookController extends BaseController { private static final long serialVersionUID = 1L; BookService bookservice; @Override public void init() throws ServletException { bookservice=CtxUtil.getBean(BookService.class); } public String ListBook(HttpServletRequest request,HttpServletResponse response){ request.setAttribute("books", bookservice.getAllBooks()); return "ListBook.jsp"; } public String Delete(HttpServletRequest request,HttpServletResponse response){ int id=Integer.parseInt(request.getParameter("id")); request.setAttribute("message", bookservice.delete(id)>0?"刪除成功!":"刪除失敗!"); request.setAttribute("books", bookservice.getAllBooks()); return "ListBook.jsp"; } }
11.2、定義視圖ListBook.jsp,用於完成圖書管理,實現圖書的列表與刪除功能,頁面如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>圖書管理</title> </head> <body> <h2>圖書管理</h2> <table border="1" width="100%"> <tr> <th>編號</th> <th>書名</th> <th>價格</th> <th>出版日期</th> <th>操作</th> </tr> <c:forEach var="book" items="${books}"> <tr> <td>${book.id}</td> <td>${book.title}</td> <td>${book.price}</td> <td>${book.publishDate}</td> <td><a href="BookController.do?act=Delete&id=${book.id}">刪除</a> </td> </tr> </c:forEach> </table> <p >${message}</p> </body> </html>
11.3、定義index.jsp頁面,讓其轉發到指定的控制器(有點類似路由功能了),頁面代碼如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <jsp:forward page="BookController.do?act=ListBook"></jsp:forward>
九、測試運行