1. 项目背景
该系统功能主要分三期完成。
一期完成仓储管理(包括:采购单、仓库、货物、条形码、入库、出库、退货、盘点、库存、库存上限报警、统计查询)和展会管理(包括:展会管理、出单管理),形成货物统一数字化管理。
二期完成货运全流程管理,包括购销合同、出货表统计、出口报运单、HOME装箱单、装箱单、委托书、发票、财务统计等。
三期完成决策分析(包括:成本分析图、销售情况统计、重点客户、经营情况同期比对统计、工作绩效),为公司经营决策提供数据支持。
2. 了解系统概貌
数据库建模,这里使用PowerDesinger建模,据我了解目前大多数使用这个。(PowerDesinger下载链接)
项目开发环境
1. Myeclipse2015IDE
2. JDK1.7.0
3.TOMCAT 8.0
4.Oracle11g、MySql5.1.39数据库
5. Maven3.3.9
6. PowerDesigner12.5(数据库的设计)
创建Maven工程,自动导入依赖jar
关于更多的Maven,在另外的一篇关于Maven的文章中写出。
在Pom文件中添加相应的dependencies:
<span style="white-space:pre"> </span><properties>
<org.springframework.version>3.2.6.RELEASE</org.springframework.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${org.springframework.version}</version>
<type>jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!-- oracle版权,必须从oracle安装中找出jdbc jar,手工建立目录(坐标),将jar拷贝到目录下 -->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.1.0.7.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.39</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.13</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>jfree</groupId>
<artifactId>jfreechart</artifactId>
<version>1.0.13</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>jstl-api</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>jstl-impl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>jexcelapi</groupId>
<artifactId>jxl</artifactId>
<version>2.4.2</version>
</dependency>
<!-- 防止和tomcat中的jar冲突 -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>servlet-api</artifactId>
<version>6.0.33</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-servlet_3.0_spec</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
由于maven连接国外的速度太慢,必须使用国内的镜像网站,我之前就是因为这个折腾了半天这里提供一个阿里的,体验飞一般的感觉
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
关于镜像的使用方法,这里不再赘述 。
创建好maven工程并且导入相应的jar包之后,修改以下几个地方:
接下来就要进行文件的配置了;
文件的添加及具体应用这里不进行详细说明。
添加 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:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd ">
<!-- 1.扫描包,controller -->
<context:component-scan base-package="com.wakev.xx.controller"/>
<!-- 2.视图解析器,jspViewResolver -->
<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages"/>
<property name="suffix" value=""/>
</bean>
</beans>
添加beans.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:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd ">
<!-- 1.扫描包service,dao -->
<context:component-scan base-package="com.wakev.xx.service,com.wakev.xx.dao"/>
<!-- 2.数据库链接jdbc.properties文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 3.数据源DataSource -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClassName}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maxPoolSize" value="${c3p0.pool.maxPoolSize}"/>
<property name="minPoolSize" value="${c3p0.pool.minPoolSize}"/>
<property name="initialPoolSize" value="${c3p0.pool.initialPoolSize}"/>
<property name="acquireIncrement" value="${c3p0.pool.acquireIncrement}"/>
</bean>
<!-- 4.Session工厂 SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- 跟mybatis进行整合 -->
<property name="configLocation" value="classpath:sqlMapConfig.xml"/>
<property name="mapperLocations" value="classpath:com/wakev/xx/mapper/*.xml"/>
</bean>
<!-- 5.事务tx -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="view*" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut expression="execution(* com.wakev.xx.service.*.*(..))" id="txPointCut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
</beans>
然后添加对应的 jdbc.properties文件
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/wakev?characterEncoding=utf-8
jdbc.username=root
jdbc.password=root
c3p0.pool.maxPoolSize=20
c3p0.pool.minPoolSize=5
c3p0.pool.initialPoolSize=3
c3p0.pool.acquireIncrement=2
以及 sqlMapConfig.xml文件,这几个文件都放在resource目录下。
<?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>
</configuration>
由于跟spring整合了,所以sqlMapConfig.xml暂时不做填写。
配置web.xml
<?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" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" 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">
<!-- spring listener -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:beans.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- DispatcherServlet springMVC -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
</web-app>
接着导入一下封装了crud等功能的文件。关于这些文件的具体详细功能讲解将在另外一篇文章中进行详细描写。
1.BaseDao接口
package com.wakev.xx.dao;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import com.wakev.xx.pagination.Page;
/**
* @Description: 泛型类,基础的DAO接口
* @Author: wakev
*/
public interface BaseDao<T> {
public List<T> findPage(Page page); //分页查询
public List<T> find(Map paraMap); //带条件查询,条件可以为null,既没有条件;返回list对象集合
public T get(Serializable id); //只查询一个,常用于修改
public void insert(T entity); //插入,用实体作为参数
public void update(T entity); //修改,用实体作为参数
public void deleteById(Serializable id); <span style="white-space:pre"> </span>//按id删除,删除一条;支持整数型和字符串类型ID
public void delete(Serializable[] ids); <span style="white-space:pre"> </span>//批量删除;支持整数型和字符串类型ID
}
BaseDaoImpl实现类
package com.wakev.xx.dao.impl;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.beans.factory.annotation.Autowired;
import com.wakev.xx.dao.BaseDao;
import com.wakev.xx.pagination.Page;
public abstract class BaseDaoImpl<T> extends SqlSessionDaoSupport implements BaseDao<T>{
@Autowired
//mybatis-spring 1.0无需此方法;mybatis-spring1.2必须注入。
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory){
super.setSqlSessionFactory(sqlSessionFactory);
}
private String ns; //命名空间
public String getNs() {
return ns;
}
public void setNs(String ns) {
this.ns = ns;
}
public List<T> findPage(Page page){
List<T> oList = this.getSqlSession().selectList(ns + ".findPage", page);
return oList;
}
public List<T> find(Map map) {
List<T> oList = this.getSqlSession().selectList(ns + ".find", map);
return oList;
}
public T get(Serializable id) {
return this.getSqlSession().selectOne(ns + ".get", id);
}
public void insert(T entity) {
this.getSqlSession().insert(ns + ".insert", entity);
}
public void update(T entity) {
this.getSqlSession().update(ns + ".update", entity);
}
public void deleteById(Serializable id) {
this.getSqlSession().delete(ns + ".deleteById", id);
}
public void delete(Serializable[] ids) {
this.getSqlSession().delete(ns + ".delete", ids);
}
}
BaseController
package com.wakev.xx.controller;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
/**
* @Description:
* @Author: wakev
*/
public abstract class BaseController {
@InitBinder
//此方法用于日期的转换,如果未加,当页面日期格式转换错误,将报400错误,实际是因为此方法
public void initBinder(WebDataBinder binder) {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(true);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
}
}
package com.wakev.xx.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HomeController {
//系统首页模块
@RequestMapping(value={"/home.action"}) //配合web下<url-pattern>/</url-pattern>
public String login(){
return "/index.jsp"; //首页,删除根目录下index.jsp,否则上面url将被拦截进不来
}
@RequestMapping(value="/fmain.action")
public String fmain(){
return "/home/fmain.jsp";
}
@RequestMapping(value="/title.action")
public String title(){
return "/home/title.jsp";
}
@RequestMapping(value="/left.action")
public String left(){
return "/home/left.jsp";
}
@RequestMapping(value="/main.action")
public String main(){
return "/home/olmsgList.jsp"; //首页转向留言板
}
//系统管理模块
@RequestMapping("/sysadminMain.action")
public String sysadminMain(){
return "/sysadmin/main.jsp";
}
@RequestMapping("/sysadminLeft.action")
public String sysadminLeft(){
return "/sysadmin/left.jsp";
}
//基础信息模块
@RequestMapping("/baseinfoMain.action")
public String baseinfoMain(){
return "/baseinfo/main.jsp";
}
@RequestMapping("/baseinfoLeft.action")
public String baseinfoLeft(){
return "/baseinfo/left.jsp";
}
//货运管理模块
@RequestMapping("/cargoMain.action")
public String cargoMain(){
return "/cargo/main.jsp";
}
@RequestMapping("/cargoLeft.action")
public String cargoLeft(){
return "/cargo/left.jsp";
}
}
分页类Page
package com.wakev.xx.pagination;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 对分页的基本数据进行一个简单的封装
*/
public class Page<T> {
private int pageNo = 1; //页码,默认是第一页
private int pageSize = 3; //每页显示的记录数,默认是10
private int totalRecord; //总记录数
private int totalPage; //总页数
private List<T> results; //对应的当前页记录
private Map<String, Object> params = new HashMap<String, Object>(); //其他的参数我们把它分装成一个Map对象
public int getPageNo() {
return pageNo;
}
public void setPageNo(int pageNo) {
this.pageNo = pageNo;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public int getTotalRecord() {
return totalRecord;
}
public void setTotalRecord(int totalRecord) {
this.totalRecord = totalRecord;
//在设置总页数的时候计算出对应的总页数,在下面的三目运算中加法拥有更高的优先级,所以最后可以不加括号。
int totalPage = totalRecord%pageSize==0 ? totalRecord/pageSize : totalRecord/pageSize + 1;
this.setTotalPage(totalPage);
}
public int getTotalPage() {
return totalPage;
}
public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
}
public List<T> getResults() {
return results;
}
public void setResults(List<T> results) {
this.results = results;
}
public Map<String, Object> getParams() {
return params;
}
public void setParams(Map<String, Object> params) {
this.params = params;
}
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Page [pageNo=").append(pageNo).append(", pageSize=").append(pageSize).append(", results=").append(results).append(", totalPage=").append(totalPage).append(", totalRecord=").append(totalRecord).append("]");
return builder.toString();
}
/* 页面链接 */
public String pageLinks(String url) {
int endPage = this.totalRecord/pageSize +1;
StringBuffer sBuf = new StringBuffer();
sBuf.append("<input type=\"hidden\" name=\"pageNo\" value=\"").append(this.pageNo).append("\">"); //分页参数:当前页隐藏域
sBuf.append("<span class=\"noprint\" style=\"padding:5px;\">");
sBuf.append(" 第").append(this.pageNo).append("页 / 共").append(endPage).append("页 ");
sBuf.append(" 总共").append(this.totalRecord).append("条记录 每页").append(this.pageSize).append("条记录 ");
sBuf.append("<a href=\"").append(url).append("?pageNo=1");
sBuf.append("\">[首页]");
sBuf.append("</a> ");
sBuf.append("<a href=\"").append(url).append("?pageNo=");
if(pageNo<=1){
sBuf.append(1);
}else{
sBuf.append(pageNo-1);
}
sBuf.append("\">[上一页]");
sBuf.append("</a> ");
sBuf.append("<a href=\"").append(url).append("?pageNo=");
if(pageNo>=endPage){
sBuf.append(endPage);
}else{
sBuf.append(pageNo+1);
}
sBuf.append("\">[下一页]");
sBuf.append("</a> ");
sBuf.append("<a href=\"").append(url).append("?pageNo=").append(endPage);
sBuf.append("\">[末页]");
sBuf.append("</a> ");
sBuf.append("</span>");
return sBuf.toString();
}
}
分页拦截器PageInterceptor
package com.wakev.xx.pagination;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
/**
*
* 分页拦截器,用于拦截需要进行分页查询的操作,然后对其进行分页处理。 利用拦截器实现Mybatis分页的原理:
* 要利用JDBC对数据库进行操作就必须要有一个对应的Statement对象
* ,Mybatis在执行Sql语句前就会产生一个包含Sql语句的Statement对象,而且对应的Sql语句
* 是在Statement之前产生的,所以我们就可以在它生成Statement之前对用来生成Statement的Sql语句下手。
* 在Mybatis中Statement语句是通过RoutingStatementHandler对象的prepare方法生成的。
*
* 所以利用拦截器实现Mybatis分页的一个思路就是拦截StatementHandler接口的prepare方法,
* 然后在拦截器方法中把Sql语句改成对应的分页查询Sql语句, 之后再调用
* StatementHandler对象的prepare方法,即调用invocation.proceed()。
*
* 对于分页而言,在拦截器里面我们还需要做的一个操作就是统计满足当前条件的记录一共有多少,
* 这是通过获取到了原始的Sql语句后,把它改为对应的统计语句,再利用Mybatis封装好的参数和设置参数的功能把Sql语句中的参数进行替换,
* 之后再执行查询记录数的Sql语句进行总记录数的统计。
*
*/
@Intercepts({ @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) })
public class PageInterceptor implements Interceptor {
private String databaseType; // 数据库类型,不同的数据库有不同的分页方法
// 拦截后要执行的方法
public Object intercept(Invocation invocation) throws Throwable {
// 对于StatementHandler其实只有两个实现类,一个是RoutingStatementHandler,另一个是抽象类BaseStatementHandler,
// BaseStatementHandler有三个子类,分别是SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler,
// SimpleStatementHandler是用于处理Statement的,PreparedStatementHandler是处理PreparedStatement的,而CallableStatementHandler是处理CallableStatement的。
// Mybatis在进行Sql语句处理的时候都是建立的RoutingStatementHandler,而在RoutingStatementHandler里面拥有一个StatementHandler类型的delegate属性,
// RoutingStatementHandler会依据Statement的不同建立对应的BaseStatementHandler,即SimpleStatementHandler、
// PreparedStatementHandler或CallableStatementHandler,在RoutingStatementHandler里面所有StatementHandler接口方法的实现都是调用的delegate对应的方法。
// 我们在PageInterceptor类上已经用@Signature标记了该Interceptor只拦截StatementHandler接口的prepare方法,
// 又因为Mybatis只有在建立RoutingStatementHandler的时候是通过Interceptor的plugin方法进行包裹的,所以我们这里拦截到的目标对象肯定是RoutingStatementHandler对象。
RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
// 通过反射获取到当前RoutingStatementHandler对象的delegate属性
StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(handler, "delegate");
// 获取到当前StatementHandler的
// boundSql,这里不管是调用handler.getBoundSql()还是直接调用delegate.getBoundSql()结果是一样的,因为之前已经说过了
// RoutingStatementHandler实现的所有StatementHandler接口方法里面都是调用的delegate对应的方法。
BoundSql boundSql = delegate.getBoundSql();
// 拿到当前绑定Sql的参数对象,就是我们在调用对应的Mapper映射语句时所传入的参数对象
Object obj = boundSql.getParameterObject();
// 这里我们简单的通过传入的是Page对象就认定它是需要进行分页操作的。
// if(obj instanceof Map){
// Map map = (Map)obj;
// Page<?> page = (Page<?>) map.get("page");
// }
if (obj instanceof Page<?>) { //拦截参数为Page对象的,其他放过
Page<?> page = (Page<?>) obj;
// 通过反射获取delegate父类BaseStatementHandler的mappedStatement属性
MappedStatement mappedStatement = (MappedStatement) ReflectUtil
.getFieldValue(delegate, "mappedStatement");
// 拦截到的prepare方法参数是一个Connection对象
Connection connection = (Connection) invocation.getArgs()[0];
// 获取当前要执行的Sql语句,也就是我们直接在Mapper映射语句中写的Sql语句
String sql = boundSql.getSql();
// 给当前的page参数对象设置总记录数
this.setTotalRecord(page, mappedStatement, connection);
// 获取分页Sql语句
String pageSql = this.getPageSql(page, sql);
// 利用反射设置当前BoundSql对应的sql属性为我们建立好的分页Sql语句
ReflectUtil.setFieldValue(boundSql, "sql", pageSql);
}
return invocation.proceed();
}
/**
* 拦截器对应的封装原始对象的方法
*/
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
/**
* 设置注册拦截器时设定的属性
*/
public void setProperties(Properties properties) {
this.databaseType = properties.getProperty("databaseType");
}
/**
* 根据page对象获取对应的分页查询Sql语句,这里只做了两种数据库类型,Mysql和Oracle 其它的数据库都 没有进行分页
*
* @param page 分页对象
* @param sql 原sql语句
* @return
*/
private String getPageSql(Page<?> page, String sql) {
StringBuffer sqlBuffer = new StringBuffer(sql);
if ("mysql".equalsIgnoreCase(databaseType)) {
return getMysqlPageSql(page, sqlBuffer);
} else if ("oracle".equalsIgnoreCase(databaseType)) {
return getOraclePageSql(page, sqlBuffer);
}
return sqlBuffer.toString();
}
/**
* 获取Mysql数据库的分页查询语句
*
* @param page
* 分页对象
* @param sqlBuffer
* 包含原sql语句的StringBuffer对象
* @return Mysql数据库分页语句
*/
private String getMysqlPageSql(Page<?> page, StringBuffer sqlBuffer) {
// 计算第一条记录的位置,Mysql中记录的位置是从0开始的。
int offset = (page.getPageNo() - 1) * page.getPageSize();
sqlBuffer.append(" limit ").append(offset).append(",").append(page.getPageSize());
return sqlBuffer.toString();
}
/**
* 获取Oracle数据库的分页查询语句
*
* @param page
* 分页对象
* @param sqlBuffer
* 包含原sql语句的StringBuffer对象
* @return Oracle数据库的分页查询语句
*/
private String getOraclePageSql(Page<?> page, StringBuffer sqlBuffer) {
// 计算第一条记录的位置,Oracle分页是通过rownum进行的,而rownum是从1开始的
int offset = (page.getPageNo() - 1) * page.getPageSize() + 1;
sqlBuffer.insert(0, "select u.*, rownum r from (").append(") u where rownum < ").append(offset + page.getPageSize());
sqlBuffer.insert(0, "select * from (").append(") where r >= ").append(offset);
// 上面的Sql语句拼接之后大概是这个样子:
// select * from (select u.*, rownum r from (select * from t_user) u
// where rownum < 31) where r >= 10
return sqlBuffer.toString();
}
/**
* 给当前的参数对象page设置总记录数
*
* @param page Mapper映射语句对应的参数对象
* @param mappedStatement Mapper映射语句
* @param connection 当前的数据库连接
*/
private void setTotalRecord(Page<?> page, MappedStatement mappedStatement,
Connection connection) {
// 获取对应的BoundSql,这个BoundSql其实跟我们利用StatementHandler获取到的BoundSql是同一个对象。
// delegate里面的boundSql也是通过mappedStatement.getBoundSql(paramObj)方法获取到的。
BoundSql boundSql = mappedStatement.getBoundSql(page);
// 获取到我们自己写在Mapper映射语句中对应的Sql语句
String sql = boundSql.getSql();
// 通过查询Sql语句获取到对应的计算总记录数的sql语句
String countSql = this.getCountSql(sql);
// 通过BoundSql获取对应的参数映射
List<ParameterMapping> parameterMappings = boundSql .getParameterMappings();
// 利用Configuration、查询记录数的Sql语句countSql、参数映射关系parameterMappings和参数对象page建立查询记录数对应的BoundSql对象。
BoundSql countBoundSql = new BoundSql(
mappedStatement.getConfiguration(), countSql,
parameterMappings, page);
// 通过mappedStatement、参数对象page和BoundSql对象countBoundSql建立一个用于设定参数的ParameterHandler对象
ParameterHandler parameterHandler = new DefaultParameterHandler(
mappedStatement, page, countBoundSql);
// 通过connection建立一个countSql对应的PreparedStatement对象。
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
pstmt = connection.prepareStatement(countSql);
// 通过parameterHandler给PreparedStatement对象设置参数
parameterHandler.setParameters(pstmt);
// 之后就是执行获取总记录数的Sql语句和获取结果了。
rs = pstmt.executeQuery();
if (rs.next()) {
int totalRecord = rs.getInt(1);
// 给当前的参数page对象设置总记录数
page.setTotalRecord(totalRecord);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (rs != null)
rs.close();
if (pstmt != null)
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 根据原Sql语句获取对应的查询总记录数的Sql语句
*
* @param sql
* @return
*/
private String getCountSql(String sql) {
int index = sql.indexOf("from");
return "select count(*) " + sql.substring(index);
}
/**
* 利用反射进行操作的一个工具类
*
*/
private static class ReflectUtil {
/**
* 利用反射获取指定对象的指定属性
*
* @param obj
* 目标对象
* @param fieldName
* 目标属性
* @return 目标属性的值
*/
public static Object getFieldValue(Object obj, String fieldName) {
Object result = null;
Field field = ReflectUtil.getField(obj, fieldName);
if (field != null) {
field.setAccessible(true);
try {
result = field.get(obj);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return result;
}
/**
* 利用反射获取指定对象里面的指定属性
*
* @param obj
* 目标对象
* @param fieldName
* 目标属性
* @return 目标字段
*/
private static Field getField(Object obj, String fieldName) {
Field field = null;
for (Class<?> clazz = obj.getClass(); clazz != Object.class; clazz = clazz
.getSuperclass()) {
try {
field = clazz.getDeclaredField(fieldName);
break;
} catch (NoSuchFieldException e) {
// 这里不用做处理,子类没有该字段可能对应的父类有,都没有就返回null。
}
}
return field;
}
/**
* 利用反射设置指定对象的指定属性为指定的值
*
* @param obj
* 目标对象
* @param fieldName
* 目标属性
* @param fieldValue
* 目标值
*/
public static void setFieldValue(Object obj, String fieldName,
String fieldValue) {
Field field = ReflectUtil.getField(obj, fieldName);
if (field != null) {
try {
field.setAccessible(true);
field.set(obj, fieldValue);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
接着就是传入前端页面了,这里将会打包上传。
到这里,框架算是基本搭建完成了,接下来就要开始开发了。