前言
今天是2018年3月16日,SSM框架学了有一段时间了,但一直没有自己独立完成一个项目,自己对SSM的理解也仅仅停留在会简单使用的层次上。很多概念只有在实际操作中才会清晰,所以想从个人网站的搭建开始,慢慢从使用SSM走向了解到最后深入底层实现。在SSM框架、SpringBoot等Java框架之外,后端开发还有一众优良的实践:缓存、分布式、集群等,囿于现在水平的限制,也为了体现从简到繁这个主题,我会在以后代码重构的过程中,一步步学习使用这些工具,使用上这些技术。每次大版本的迭代,我都会把代码上传到Github,项目搭建的全过程也会记录在此。
项目概述
本项目是一个基于Java SSM框架开发的单用户个人博客系统,具有博客发表、修改、评论、后台管理等功能。项目分为前台和后台,前台负责展示业务,前后端适当分离;后台需要登录,负责文章、评论增删查改业务。
开发环境搭建
1、开发工具
- CentOS 7.4
- JDK 1.8.0_162
- Intellij Idea 2017.3 (Idea确实是个好东西,的确比Eclipse好用一些)
- Apache - Tomcat 8.5.29
- MySQL 5.7
2、新建项目
- 选择Maven(一个项目构建、依赖管理工具,我下载的Idea里自带Maven插件。不了解的可参考这篇文章:Maven使用入门。另外为了加快jar包下载速度可以去.m2文件夹下面的setting.xml配置下载源,改为国内镜像),勾选Create from archetype,选择maven-archetype-webapp这个模版,点击Next。
- 填写GroupId、ArtifactId,点击Next。
- 选择本地Maven仓库位置,点击右侧的+号,然后添加一个属性archetypeCatalog=internal。至于为什么要添加可以看这篇文章:解决IDEA 创建Maven项目速度慢问题。
- 继续点击Netxt、Fininsh、Import Changes,如果Idea的控制台里出现BUILD SUCCESS的字样,那么说明项目初步创建成功了。但此时目录结构还不适合SSM项目,我们把目录修改成如下所示,右键选择Mark Directory as标记文件夹的作用:
- 配置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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.vansl</groupId>
<artifactId>myblog</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>myblog</name>
<url>http://maven.apache.org</url>
<!--版本号-->
<properties>
<junit.version>4.12</junit.version>
<slf4j.version>1.7.21</slf4j.version>
<mysql.driver.version>5.1.35</mysql.driver.version>
<servlet.version>3.1.0</servlet.version>
<jsp.version>2.2</jsp.version>
<jstl.version>1.2</jstl.version>
<spring.version>4.3.3.RELEASE</spring.version>
<aspect.version>1.5.4</aspect.version>
<mybatis.version>3.4.0</mybatis.version>
<mybatis.spring.version>1.3.0</mybatis.spring.version>
<commons-dbcp.version>1.4</commons-dbcp.version>
<pagehelper.version>4.0.0</pagehelper.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!--项目依赖 -->
<dependencies>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!--日志-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!--mysql驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.driver.version}</version>
</dependency>
<!--j2ee相关包-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>${jsp.version}</version>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>${jstl.version}</version>
</dependency>
<!-- spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspect.version}</version>
</dependency>
<!-- mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis.spring.version}</version>
</dependency>
<!--用于在applicationContext.xml中配置数据库 -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>${commons-dbcp.version}</version>
</dependency>
<!-- Mybatis分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>${pagehelper.version}</version>
</dependency>
<!-- 上传组件-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<!-- json -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<!--工具包-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<!--UEetior-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20160212</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 配置tomcat插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat8-maven-plugin</artifactId>
<configuration>
<port>80</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
</project>
- 配置web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<!-- 监听器加载context-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 加载spring容器 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/applicationContext-*.xml</param-value>
</context-param>
<!-- 编码过滤器 -->
<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>
<!-- log4j日志输出 -->
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath:config/log4j.properties</param-value>
</context-param>
<!--配置DispatcherServlet-->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--SpringMVC配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<!--配置DispatcherServlet映射-->
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
- 在resources文件夹下新建spring文件夹,配置spring-mvc.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: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/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 启用自动扫描 -->
<context:component-scan base-package="com.vansl.controller" />
<!-- 设置配置方案 -->
<mvc:annotation-driven />
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>
<!-- 静态资源映射 -->
<mvc:resources location="/WEB-INF/statics/css/" mapping="/css/**"/>
<mvc:resources location="/WEB-INF/statics/js/" mapping="/js/**"/>
<mvc:resources location="/WEB-INF/statics/images/" mapping="/images/**"/>
<!--文件上传解析器 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设定默认编码 -->
<property name="defaultEncoding" value="UTF-8"></property>
<!-- 设定文件上传的最大值5MB,5*1024*1024 -->
<property name="maxUploadSize" value="5227520"></property>
</bean>
</beans>
- 配置Spring容器,在spring文件夹下面新建applicationContext-service.xml、applicationContext-trans.xml、applicationContext-dao.xml,首先配置applicationContext-service.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<!-- 扫描包加载Service实现类 -->
<context:component-scan base-package="com.vansl.service"></context:component-scan>
</beans>
- 然后配置用于事务管理的applicationContext-trans.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<!-- 事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 数据源 -->
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 传播行为 -->
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="create*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
<tx:method name="select*" propagation="SUPPORTS" read-only="true" />
<tx:method name="get*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- 切面 -->
<aop:config>
<aop:advisor advice-ref="txAdvice"
pointcut="execution(* com.vansl.service.*.*(..))" />
</aop:config>
</beans>
- 最后配置applicationContext-dao.xml,并新建mybatis和config文件夹分别用于存放sqlSessionFactory配置文件、数据库和日志及其他配置文件:
applicationContext-dao.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<!-- 加载配置文件 -->
<context:property-placeholder location="classpath:config/*.properties" />
<!-- 数据库连接池 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- 使用配置文件的配置 -->
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="initialSize" value="10" />
<property name="maxActive" value="10" />
<property name="maxIdle" value="20" />
<property name="minIdle" value="5" />
</bean>
<!-- 配置sqlsessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 加载配置文件 -->
<property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml"></property>
<property name="dataSource" ref="dataSource"></property>
<!-- 自动扫描mapper.xml文件 -->
<property name="mapperLocations" value="classpath*:com/vansl/mapper/*.xml"></property>
</bean>
<!-- 自动扫描dao接口并实例化 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.vansl.dao"></property>
</bean>
</beans>
SqlMapConfig.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>
<plugins>
<!-- Mybatis分页插件 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<!-- 设置数据库类型 Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL六种数据库-->
<property name="dialect" value="mysql"/>
</plugin>
</plugins>
</configuration>
数据库和日志配置文件这里不再给出。
接着配置tomcat服务器,点击运行,项目成功运行,此时浏览器显示404。接下来在controller包下新建HomeController.java,并在views下新建index.jsp:
package com.vansl.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
/**
* @author: vansl
* @create: 18-3-17 下午11:58
*/
@Controller
public class HomeController {
//添加日志器
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
//action映射
@RequestMapping("/index")
public String index(HttpServletRequest request){
//输出日志文件
logger.info("Build Success");
//返回index.jsp
return "index";
}
}
重启tomcat,访问http://localhost:8080/index,如果出现index.jsp,那么说明开发环境搭建成功了(另外在有欢迎页的时候输入http://localhost:8080也能访问index.jsp,原因不明,有待解释:点击打开链接)。
数据库设计
初步设计了四张表以实现基本的登录、发表博客、评论以及管理功能。如下所示:
虽然是单用户的个人博客系统,但我还是设计了user这张表,并添加了用户角色字段进行用户权限管理。一是为了增加开发的完整度、复杂度,其次也可以方便拓展成多用户的系统。由此建表:
/**
* @DATE 20:47 2018/3/21
*/
--
-- 数据库: `myblog`
--
--
-- 用户表
--
CREATE TABLE `user` (
`id` INT NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` VARCHAR(30) NOT NULL COMMENT '用户名',
`password` VARCHAR(30) NOT NULL COMMENT '用户密码',
`role` VARCHAR(20) DEFAULT 'user' COMMENT '用户权限',
UNIQUE (`username`),
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;
--
-- 博客表
--
CREATE TABLE `blog` (
`id` INT NOT NULL AUTO_INCREMENT COMMENT '博客ID',
`title` VARCHAR(90) NOT NULL COMMENT '博客标题',
`time` DATE NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '发表时间',
`content` TEXT NOT NULL COMMENT '博客正文(包含html格式)',
`text` TEXT NOT NULL COMMENT '博客正文(纯文本)',
`pv` int NOT NULL COMMENT '博客点击量',
`publish` INT(1) NOT NULL COMMENT '是否已发表,0已发表,1未发表(在草稿箱)',
`user_id` INT NOT NULL COMMENT '用户ID',
`type_id` INT NOT NULL COMMENT '分类ID',
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;
--
-- 博客分类表
--
CREATE TABLE `blog_type` (
`id` INT NOT NULL AUTO_INCREMENT COMMENT '分类ID',
`type_name` VARCHAR(30) NOT NULL COMMENT '分类名称',
`parent_id` INT NOT NULL COMMENT '父分类id',
`user_id` INT NOT NULL COMMENT '用户ID',
UNIQUE (`type_name`,`parent_id`),
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;
--
-- 评论表
--
CREATE TABLE `blog_comment` (
`id` INT NOT NULL AUTO_INCREMENT COMMENT '评论ID',
`time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '发表时间',
`name` VARCHAR(30) NOT NULL COMMENT '评论者',
`contact` VARCHAR(90) NOT NULL COMMENT '联系方式',
`ip` VARCHAR(15) NOT NULL COMMENT '评论者ip',
`address` TINYTEXT NOT NULL COMMENT '评论者地址',
`content` TEXT NOT NULL COMMENT '评论内容',
`blog_id` INT NOT NULL COMMENT '博客ID',
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;
把sql文件导入数据库:
执行成功。至此开发环境搭建和数据库表设计部分的工作就完成了。
参考
https://www.cnblogs.com/Sinte-Beuve/p/5730553.html