ssm实际上是三种框架的集成,它们分别是Spring,SpringMVC以及mybatis。笔者的个人见解更愿意认为ssm是一个优秀的平台,在这个平台上开发者可以更有效率的实现CRUD,制定诸多事务以及进行成本更小的维护等操作。
Spring是非常经典的java bean容器,它可以非常有效率的实现对java bean(也包括分离出的DAO和Service)的管理。
以往笔者在开发的过程中,比方在Student类中要用到Teacher类中的checkName方法,而checkName方法中用到NameBook类中的showName方法(假设上述方法均不为静态,则需要非静态调用),笔者的第一反应肯定是在Student类中通过关键字new创建一个Teacher实例,在teacher类中new出NameBook实例,再通过teacher.checkName和nameBook.showName来实现对其他类中非静态方法的调用。
上述过程中,其实Student类对Teacher类和NameBook类产生了依赖,这种依赖关系通常被称为耦合,通常耦合对后期的维护是非常不利的,比如NameBook类中的showName方法发生了改变,会影响到student类的运作,对预想的业务逻辑会产生不良干扰,所以这里笔者个人认为Spring的诞生很大一部分功能是为了解耦,使用了Spring之后不再通过new关键字创建实例的方式来调用,而是管理bean的权限移交给Spring容器,bean会以被动的形式在需要的时候被注入到相应的类中去,从而实现相应的逻辑。这一过程实际也叫作IOC(控制反转),是Spring的核心内容之一。
Springmvc作为控制层的web框架,全方面替代了Struts2,在action层面进行拦截,使用注解的方式来定义controller中的antion,在xml中配置自动扫描即可实现业务的跳转。并且由于是spring大家族的产品,对spring的兼容性几乎到达百分之百兼容。
mybatis是当下较为流行的持久层框架,相比于hibernate,我个人觉得mybatis更加轻量级,它抛弃了传统的硬编码编写sql语句的方式(硬编码指的是sql语句往往被写死在类文件中,不利于扩展)。mybatis采用xml配置sql语句的方式,sql形式多样,便于扩展和后期维护。
本文讲解搭建上述三大框架集成的maven工程项目。
IDE:myeclipse,JDK版本:1.7,启动方式:tomcat。
首先在myeclipse中创建maven工程并转换成maven WebApp,具体步骤参考我之前的博文maven安装配置与创建工程,这里不再赘述。
下面是需要在pom.xml文件中添加的依赖,可能部分jar包多余,但是本项目不冲突:
4.0.0
ssm1
ssm1
0.0.1-SNAPSHOT
war
ssm1
UTF-8
UTF-8
4.2.5.RELEASE
3.2.8
5.1.29
1.7.18
1.2.17
jstl
jstl
1.2
javax
javaee-api
7.0
junit
junit
4.11
test
org.springframework
spring-core
${spring.version}
org.springframework
spring-web
${spring.version}
org.springframework
spring-oxm
${spring.version}
org.springframework
spring-tx
${spring.version}
org.springframework
spring-jdbc
${spring.version}
org.springframework
spring-webmvc
${spring.version}
org.springframework
spring-context
${spring.version}
org.springframework
spring-context-support
${spring.version}
org.springframework
spring-aop
${spring.version}
org.springframework
spring-test
${spring.version}
org.aspectj
aspectjweaver
1.8.10
org.mybatis
mybatis
${mybatis.version}
org.mybatis
mybatis-spring
1.2.2
mysql
mysql-connector-java
${mysql-driver.version}
c3p0
c3p0
0.9.1.2
com.alibaba
fastjson
1.1.41
log4j
log4j
${log4j.version}
org.slf4j
slf4j-api
${slf4j.version}
org.slf4j
slf4j-log4j12
${slf4j.version}
org.codehaus.jackson
jackson-mapper-asl
1.9.13
com.fasterxml.jackson.core
jackson-core
2.8.0
com.fasterxml.jackson.core
jackson-databind
2.8.0
commons-fileupload
commons-fileupload
1.3.1
commons-io
commons-io
2.4
commons-codec
commons-codec
1.9
com.github.abel533
ECharts
3.0.0
com.google.code.gson
gson
2.5
compile
true
net.sf.jxls
jxls-core
1.0.5
ssm1
org.eclipse.m2e
lifecycle-mapping
1.0.0
org.apache.maven.plugins
maven-resources-plugin
[2.5,)
resources
maven-compiler-plugin
2.3.2
1.7
1.7
上述依赖成功添加后,进入正式开发步骤,先贴上我的项目完整目录。
总共有六个包。
上述分层的目的其实就是让控制层去调用业务层,业务层调用dao层。这么调用的目的其实就是要做到解耦,抽离出公共的业务,将各层代码之间的耦合度降到最低。而Spring会帮助我们管理这些层的接口和实现类,在有依赖需求的时候将其注入,减少了代码中new关键字的出现,大大降低耦合度。
首先在src/main/resources下新建如下几个文件:
jdbc.properties是我的数据库配置文件,具体内容如下,要根据你们自己的数据库做修改:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/你的数据库名
username=你的数据库登录账号
password=你的数据库登录密码
c3p0.pool.maxPoolSize=400
c3p0.pool.minPoolSize=50
c3p0.pool.initialPoolSize=50
c3p0.pool.acquireIncrement=100
log4j.properties是我的日志信息配置,可以不设置这个文件,具体内容如下:
log4j.rootLogger=INFO,Console,File
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Target=System.out
log4j.appender.Console.layout = org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%c] - %m%n
log4j.appender.File = org.apache.log4j.RollingFileAppender
log4j.appender.File.File = logs/ssm.log
log4j.appender.File.MaxFileSize = 10MB
log4j.appender.File.Threshold = ALL
log4j.appender.File.layout = org.apache.log4j.PatternLayout
log4j.appender.File.layout.ConversionPattern =[%p] [%d{yyyy-MM-dd HH\:mm\:ss}][%c]%m%n
applicationContenxt.xml是Spring的配置文件,其中各标签的功能都已经详尽注释,内容如下:
SpringMVC.xml是Springmvc的配置文件,根据这个文件对配置了注解controller进行扫描,内容如下:
sqlMapConfig.xml是mybatis的配置文件,但是由于我搭建的是集成框架,这个文件可以不用配置,数据库管理权转交给Spring,相关配置已在applicationContext.xml中配置过了。
最后在web.xml中配置Spring的过滤器,springmvc的前端控制分发器以及编码过滤器,内容如下:
Archetype Created Web Application
/index.jsp
contextConfigLocation
classpath:applicationContext.xml
org.springframework.web.context.ContextLoaderListener
encodingFilter
org.springframework.web.filter.CharacterEncodingFilter
true
encoding
UTF-8
encodingFilter
/*
SpringMVC
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:SpringMVC.xml
1
true
SpringMVC
/
至此,配置文件均已配置完毕,下面进入各层类文件编写。
实体层可以单独开发,根据你自己数据库里的表创建对应的实体类,下面是我的一个实体类具体代码:
package com.huang.entity;
import java.util.Date;
/**
* @author 黄政豪
* @date 2018年7月20日 下午2:44:46
* @description:实体类
*
*/
public class Retailer {
private String retailerid;
private String name;
private String telphone;
private String address;
private Date createtime;
public String getRetailerid() {
return retailerid;
}
public void setRetailerid(String retailerid) {
this.retailerid = retailerid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTelphone() {
return telphone;
}
public void setTelphone(String telphone) {
this.telphone = telphone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Date getCreatetime() {
return createtime;
}
public void setCreatetime(Date createtime) {
this.createtime = createtime;
}
}
接下来进行Dao层编写,由于dao层是三层之中的最底层,也就是最基本的一层,所以需要抽象出一个接口,接口中应该包含上层所有业务接口都能调用的增删改查方法。这里我把这个基本接口命名为BaseDao,具体代码如下:
package com.huang.dao;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import org.apache.poi.ss.formula.functions.T;
import com.huang.entity.User;
public interface BaseDao {
public T get(Serializable id);
public List find(Map map);
public void insert(T entity);
public boolean update(T entity);
public void deleteById(Serializable id);
public void delete(Serializable[] ids);
}
其中参数都指定为泛型或者是序列化接口(Serializeble)。java中大多包装类(String,Integer,Double等)基本都实现了serializable接口,因此上面这个实体中参数类型可为所有实现了序列化接口的类型。
这么做的好处就是实现类和业务层在调用时不用担心由于业务不同而导致传入参数不同的问题,我个人认为这就是软件架构设计中抽象的核心概念,实现复用。
BaseDao实现类代码如下:
package com.huang.dao.impl;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.poi.ss.formula.functions.T;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.beans.factory.annotation.Autowired;
import com.huang.dao.BaseDao;
public abstract class BaseDaoImpl extends SqlSessionDaoSupport implements BaseDao{
@Autowired
//mybatis-spring 1.0无需此方法;mybatis-spring1.2必须注入。
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory){
super.setSqlSessionFactory(sqlSessionFactory);
}
public String ns;
public String getNs() {
return ns;
}
public void setNs(String ns) {
this.ns = ns;
}
@Override
public T get(Serializable id) {
// TODO Auto-generated method stub
return this.getSqlSession().selectOne(ns+".get", id);
}
@Override
public List find(Map map) {
// TODO Auto-generated method stub
List list = this.getSqlSession().selectList(ns+".find", map);
return list;
}
@Override
public void insert(T entity) {
// TODO Auto-generated method stub
this.getSqlSession().insert(ns+".insert", entity);
}
@Override
public boolean update(T entity) {
// TODO Auto-generated method stub
this.getSqlSession().update(ns+".update", entity);
return true;
}
@Override
public void deleteById(Serializable id) {
// TODO Auto-generated method stub
this.getSqlSession().delete(ns+".deleteById", id);
}
@Override
public void delete(Serializable[] ids) {
// TODO Auto-generated method stub
this.getSqlSession().delete(ns+".delete", ids);
}
}
实现类中设定了ns参数和它的get,set方法,这么做的目的是让dao层其他实现类继承basedao实现类后能通过设置命名空间的方式让每一个dao去正确匹配自己的Mapper.xml文件。
ns+".method",ns会在每个entityDao中通过setNs设置,method务必要和后面配置的Mapper.xml中的方法id一致。
由于我使用的mybatis-spring的jar包版本是1.2,所以在实现类中必须通过注解注入setSqlSessionFactory方法,这样spring才能在其配置文件中成功加载datasource数据源。
接下来编写实体类对应的Dao,代码如下:
package com.huang.dao;
import java.io.Serializable;
import com.huang.entity.Retailer;
/**
* @author 黄政豪
* @date 2018年7月20日 下午2:46:31
* @description:
*
*/
public interface RetailerDao extends BaseDao{
public void deleteByName(Serializable name);
}
这里我在dao中还定义了额外的方法,这是根据Retailer这个实体的业务需求增加的,需要在其实现类中添加具体逻辑。RetailerDao通过继承了BaseDao后能调用父类中的方法对Retailer表进行增删改查操作。这里个人建议将基本操作抽象到basedao中,每个实体额外的业务逻辑方法定义在具体的entityDao中。
RetailerDao实现类代码如下:
package com.huang.dao.impl;
import java.io.Serializable;
import org.springframework.stereotype.Repository;
import com.huang.dao.RetailerDao;
import com.huang.entity.Retailer;
/**
* @author 黄政豪
* @date 2018年7月20日 下午2:47:09
* @description:Dao实现类
*
*/
@Repository
public class RetailerDaoImpl extends BaseDaoImpl implements RetailerDao{
public RetailerDaoImpl() {
// TODO Auto-generated constructor stub
super.setNs("com.huang.mapper.RetailerMapper");
}
@Override
public void deleteByName(Serializable name) {
// TODO Auto-generated method stub
this.getSqlSession().delete(this.getNs()+".deleteByName", name);
}
}
这里设置了命名空间,并重写了额外添加的方法。
@repository这个注解的功能是能让Spring扫描到加了该注解的Dao层组件,必须加上。
至此,Dao层编写结束。
接下来进行业务层的编写。
RetailerService代码如下:
package com.huang.service;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import com.huang.entity.Retailer;
import com.huang.entity.User;
/**
* @author 黄政豪
* @date 2018年7月20日 下午2:50:32
* @description:业务层接口
*
*/
public interface RetailerService {
public Retailer get(Serializable name);
public List find(Map map);
public void insert(Retailer retailer);
public void update(Retailer retailer);
public void deleteByName(Serializable name);
public void delete(Serializable[] names);
}
其实这里传入的参数可以具体设置为对应的基本类型或者是实体,这里我将原来的泛型改成了Retailer实体,其它还是使用serializable接口,不影响系统功能。
实现类代码如下:
package com.huang.service.impl;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.huang.dao.RetailerDao;
import com.huang.entity.Retailer;
import com.huang.service.RetailerService;
/**
* @author 黄政豪
* @date 2018年7月20日 下午2:56:10
* @description:service实现类
*
*/
@Service
public class RetailerServiceImpl implements RetailerService{
@Autowired
RetailerDao retailerDao;
@Override
public Retailer get(Serializable name) {
// TODO Auto-generated method stub
return retailerDao.get(name);
}
@Override
public List find(Map map) {
// TODO Auto-generated method stub
return retailerDao.find(map);
}
@Override
public void insert(Retailer retailer) {
// TODO Auto-generated method stub
retailerDao.insert(retailer);
}
@Override
public void update(Retailer retailer) {
// TODO Auto-generated method stub
retailerDao.update(retailer);
}
@Override
public void deleteByName(Serializable name) {
// TODO Auto-generated method stub
retailerDao.deleteByName(name);
}
@Override
public void delete(Serializable[] names) {
// TODO Auto-generated method stub
retailerDao.delete(names);
}
}
@service注解功能是让Spring扫描到加了该注解的service层组件,@AutoWired是将dao层组件注入到service层,从而service层不再需要new关键字就可以调用dao层组件。
@service注解在service层接口实现类中必须加上,service在调用dao层组件时必须用@AutoWired或者是@Resource注解让spring为service层注入dao层组件。
接下来编写映射文件Mapper.xml,代码如下:
insert into retailer
(retailerid,name,telphone,address,createtime)
values
(
#{retailerid,jdbcType=VARCHAR},
#{name,jdbcType=VARCHAR},
#{telphone,jdbcType=VARCHAR},
#{address,jdbcType=VARCHAR},
#{createtime,jdbcType=DATE}
)
update retailer
telphone=#{telphone},
address=#{address}
where name = #{name}
delete from retailer
where
name=#{name}
delete from retailer
where name in
#{name}
xml各标签中id的值必须和service以及Dao层定义的接口名一样,否则mybatis会找不到对应的方法。
另外jdbcType后面跟的类型务必大写。
Mapper namespace的值和Dao层实现类中setNs的值务必保持一致。
控制层控制页面的跳转,在Springmvc里提供了注解的方式配置页面跳转的路由。
我的控制器代码如下:
package com.huang.controller;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.huang.entity.Retailer;
import com.huang.service.RetailerService;
/**
* @author 黄政豪
* @date 2018年7月20日 下午3:01:56
* @description:retailer控制器
*
*/
@Controller
@ResponseBody
@RequestMapping("/retailer")
public class RetailerController {
@Autowired
RetailerService service;
@RequestMapping("/findOne")
public Retailer findOne(HttpServletRequest request){
String name = request.getParameter("name");
return service.get(name);
}
@RequestMapping("/find")
public List find(Retailer retailer){
Map map = new HashMap();
if(retailer.getName()!=null){
map.put("name", retailer.getName());
}
if(retailer.getTelphone()!=null){
map.put("telphone", retailer.getTelphone());
}
if(retailer.getAddress()!=null){
map.put("address", retailer.getAddress());
}
return service.find(map);
}
@RequestMapping("/addRetailer")
public String addRetailer(HttpServletRequest request){
Retailer retailer = new Retailer();
String name = request.getParameter("name");
Date date = new Date(System.currentTimeMillis());
retailer.setCreatetime(date);
if(name!=null){
String retailerid = (UUID.randomUUID().toString());
retailer.setRetailerid(retailerid);
retailer.setName(name);
if(request.getParameter("telphone")!=null){
retailer.setTelphone(request.getParameter("telphone"));
}
if(request.getParameter("address")!=null){
retailer.setAddress(request.getParameter("address"));
}
service.insert(retailer);
return " insert success";
}else {
return "name must not be null";
}
}
@RequestMapping("/update")
public String update(Retailer retailer){
if(retailer.getTelphone().equals("")&&retailer.getAddress().equals("")){
return "no data should be updated.";
}
System.out.println(retailer.getName());
service.update(retailer);
return "update success.";
}
@RequestMapping("/deleteByName")
public String delete(Retailer retailer,HttpServletRequest request){
String name = retailer.getName();
service.deleteByName(name);
return "data of "+name+" has been deleted.";
}
}
一个controller需要在类名上方配置@controller才能被spring正确的扫描。
@RequestMapping就是控制路由跳转的注解,可以只配置在方法名上方,也可以方法名和类名上方都配置,比如上面我两个地方都配置了,通过tomcat部署并启动项目,那么如果我要请求find方法内部的业务,那么需要输入的url应该是:http://localhost:8080/ssm1/retailer/find,其中ssm1是我的项目名。
如果访问的方法要求某些参数,由于这里我在RequestMapping中没有定义method的种类,所以最简单的携参形式就是用get方法在url里直接附上需要的参数,比如我访问findOne方法,要求要带有key值为name的参数,我可以通过http://localhost:8080/ssm1/retailer/findOne?name=黄政豪这样的形式成功访问。
@ResponseBody注解能够把返回的对象(object)统统转化为JSON格式输出在页面,比如上面我访问findOne方法,最后的页面上会显示如下结果:
至此,Spring+SpringMVC+mybatis的maven工程已经搭建完毕。
其中我认为尤其重要的是将Controller层和Service层进行抽象,将基础业务全部封装在Dao层。事实上抽象也是软件设计中核心的概念之一,它最终会达到解耦的目的,表现为上述项目中三层的分离。
另外Spring的控制反转和SpringMVC的注解也对上述项目起到了至关重要的作用,回顾此次搭建的过程,我认为面向接口编程的理念真的妙不可言。
上述项目的源码我已经上传到我自己的github上了,有需要参考的朋友可以去下载。传送门:至尊小宝宝的github
需要说明的是,数据库脚本我还是和以往一样没有给出,因为我觉得数据库完全是根据自己的意愿想怎么建就怎么建,只是实体类也需要对应而已,完全没必要生搬硬套我的数据库,我们的业务需求肯定是天差地别的,所以数据库还是要自己为自己的项目量身定做。如果数据库都要照样画葫芦,那我觉得真的非常可悲了。。。
最后,有不足之处还望大家补充和指正。