SpringBoot 架构搭建
上一节,我们入门了,那这一节我们讲下如何使用 SpringBoot 来搭建一个可以直接使用的系统架构。
本节内容较多,主要包括:
1、 添加 MyBatis 的一些配置
2、 连接数据库方面的知识点
3、 统一异常的处理
4、 日志的管理
下面, 我们来看看是如何配置的。
一、 添加 POM.XML 的依赖, 具体看依赖的说明。
<!-- Inherit defaults from Spring Boot --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> </parent> <!-- Add typical dependencies for a web application --> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 添加JDBC依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- 添加MySQL依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- c3p0 configuration --> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <!-- MyBatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.0</version> </dependency> <!-- MyBatis spring-boot pagehelper 的分页插件 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.2.5</version> </dependency> </dependencies> <!-- Package as an executable jar --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
说明:
1、 使用的是 MySql 数据库,依赖 Mysql的驱动, 连接池使用的是 C3P0
2、 分页插件我们引入的是 pagehelper-spring-boot-starter
二、 项目的目录结构说明
1.1 连接池的配置
首先看 yml 配置文件: 代码中都有注释。
# 数据源配置 c3p0 c3p0: datasource: jdbcUrl: jdbc:mysql://127.0.0.1:3306/springtest?characterEncoding=UTF-8 driverClass: com.mysql.jdbc.Driver user: spring password: spring # 连接池中保留的最小连接数 minPoolSize: 3 # 连接池中保留的最大连接数。Default:15 maxPoolSize: 20 # 最大空闲时间,1800秒内未使用则连接被丢弃,若为0则永不丢弃。Default:0 maxIdleTime: 1800 # 当连接池中的连接耗尽的时候c3p0一次同时获取的链接数。Default:3 acquireIncrement: 5 # 初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default:3 initialPoolSize: 3 # 每3600秒检查所有的连接池中的空闲连接。Default:0 idleConnectionTestPeriod: 3600
对应的 java 代码:
/** *@Description: c3p0 连接池 *@Author:杨攀 *@Since:2018年6月25日下午5:44:14 */ @Configuration @ConfigurationProperties(prefix = "c3p0.datasource") public class C3p0DataSourceConfig { private String driverClass; private String jdbcUrl; private String user; private String password; /** *@Fields initialPoolSize : 初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间 */ private int initialPoolSize; /** *@Fields minPoolSize : 连接池中保留的最小连接数 */ private int minPoolSize; /** *@Fields maxPoolSize : 连接池中保留的最大连接数。 */ private int maxPoolSize; /** *@Fields acquireIncrement : 当连接池中的连接耗尽的时候c3p0一次同时获取的链接数。Default:3 */ private int acquireIncrement; /** *@Fields maxIdleTime : 最大空闲时间,1800秒内未使用则连接被丢弃,若为0则永不丢弃。 */ private int maxIdleTime; /** *@Fields idleConnectionTestPeriod : 每3600秒检查所有的连接池中的空闲连接。Default:0 */ private int idleConnectionTestPeriod; @Bean public DataSource dataSource(){ ComboPooledDataSource c3p0 = null; try { c3p0 = new ComboPooledDataSource (); c3p0.setDriverClass (driverClass); c3p0.setJdbcUrl (jdbcUrl); c3p0.setUser (user); c3p0.setPassword (password); c3p0.setInitialPoolSize (initialPoolSize); c3p0.setMinPoolSize (minPoolSize); c3p0.setMaxPoolSize (maxPoolSize); c3p0.setAcquireIncrement (acquireIncrement); c3p0.setMaxIdleTime (maxIdleTime); c3p0.setIdleConnectionTestPeriod (idleConnectionTestPeriod); } catch (PropertyVetoException e) { e.printStackTrace (); } return c3p0; } public String getDriverClass(){ return driverClass; } public void setDriverClass(String driverClass){ this.driverClass = driverClass; } public String getJdbcUrl(){ return jdbcUrl; } public void setJdbcUrl(String jdbcUrl){ this.jdbcUrl = jdbcUrl; } public String getUser(){ return user; } public void setUser(String user){ this.user = user; } public String getPassword(){ return password; } public void setPassword(String password){ this.password = password; } public int getInitialPoolSize(){ return initialPoolSize; } public void setInitialPoolSize(int initialPoolSize){ this.initialPoolSize = initialPoolSize; } public int getMinPoolSize(){ return minPoolSize; } public void setMinPoolSize(int minPoolSize){ this.minPoolSize = minPoolSize; } public int getMaxPoolSize(){ return maxPoolSize; } public void setMaxPoolSize(int maxPoolSize){ this.maxPoolSize = maxPoolSize; } public int getAcquireIncrement(){ return acquireIncrement; } public void setAcquireIncrement(int acquireIncrement){ this.acquireIncrement = acquireIncrement; } public int getMaxIdleTime(){ return maxIdleTime; } public void setMaxIdleTime(int maxIdleTime){ this.maxIdleTime = maxIdleTime; } public int getIdleConnectionTestPeriod(){ return idleConnectionTestPeriod; } public void setIdleConnectionTestPeriod(int idleConnectionTestPeriod){ this.idleConnectionTestPeriod = idleConnectionTestPeriod; } }
1.2 MyBatis 的使用
1、 首先创建表,然后通过工具生产对应的 java 和 xml 文件
UserBeanMapper.java
package com.topinfo.dao; import com.github.pagehelper.Page; import com.topinfo.bean.UserBean; public interface UserBeanMapper { public int insert(UserBean bean); public UserBean selectById(Integer id); public Page<UserBean> getUserList(); }
UserBeanMapper.xml
<?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.topinfo.dao.UserBeanMapper"> <resultMap id="BaseResultMap" type="com.topinfo.bean.UserBean"> <id column="id" jdbcType="INTEGER" property="id" /> <result column="name" jdbcType="VARCHAR" property="name" /> <result column="age" jdbcType="INTEGER" property="age" /> <result column="sex" jdbcType="VARCHAR" property="sex" /> </resultMap> <sql id="Base_Column_List"> id, name, age, sex </sql> <select id="selectById" parameterType="java.lang.Integer" resultMap="BaseResultMap"> select <include refid="Base_Column_List" /> from user where id = #{id,jdbcType=VARCHAR} </select> <insert id="insert" parameterType="com.topinfo.bean.UserBean"> insert into user (id, name, age, sex) values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}, #{age,jdbcType=INTEGER}, #{sex,jdbcType=VARCHAR}) </insert> <!-- 分页查询 --> <select id="getUserList" resultMap="BaseResultMap"> select <include refid="Base_Column_List" /> from user </select> </mapper>
新建 service , 创建 接口 和 实现类
UserService.java
package com.topinfo.service; import java.util.List; import com.topinfo.bean.UserBean; public interface UserService { public int insert(UserBean bean); public UserBean getUser(int id); public List<UserBean> getUserList(int pageNo, int pageSize); }
UserServiceImpl.java
package com.topinfo.service.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import com.topinfo.bean.UserBean; import com.topinfo.dao.UserBeanMapper; import com.topinfo.service.UserService; @Service public class UserServiceImpl implements UserService { @Autowired private UserBeanMapper userBeanMapper; @Override public int insert(UserBean u){ // 保持用户信息简单示例 int result = userBeanMapper.insert (u); return result; } @Override public UserBean getUser(int id){ // 查询的简单示例 UserBean bean = userBeanMapper.selectById (id); return bean; } @Override public List<UserBean> getUserList(int pageNo,int pageSize){ //设置分页,后面必须紧跟 userBeanMapper 去查询,消费掉 PageHelper PageHelper.startPage (pageNo, pageSize); List<UserBean> list = userBeanMapper.getUserList (); return list; } }
创建 Controller 层
TestController.java
package com.topinfo.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import com.topinfo.bean.UserBean; import com.topinfo.common.exception.BusinessException; import com.topinfo.common.exception.ErrorEnum; import com.topinfo.service.UserService; @RestController public class TestController { @Autowired private UserService userService; @RequestMapping("/hello") public String home(){ return "Hello World!"; } @RequestMapping("/userAdd") public String userAdd(UserBean user){ // 测试插入 int i = userService.insert (user); int m = 5/0; return "Hello World!"; } @RequestMapping("/getUser") public UserBean getUser(int id){ // 测试查询 UserBean bean = userService.getUser (id); if(bean == null){ throw new BusinessException (ErrorEnum.USER_NOTEXIST); } return bean; } @RequestMapping("/getUserList") public List<UserBean> getUserList(int pageNo, int pageSize){ List<UserBean> list = userService.getUserList (pageNo, pageSize); return list; } }
接下来,我们在 配置 异常的处理 和 日志
三、 异常的统一处理
全局异常处理类:
package com.topinfo.common.exception; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import com.topinfo.common.utils.ResultUtil; /** *@Description: 全局异常处理 *@Author:杨攀 *@Since:2018年6月26日下午3:46:46 */ @ControllerAdvice public class GlobalExceptionHandler { protected static Logger logger = LoggerFactory.getLogger (GlobalExceptionHandler.class); /** *@Description: 普通异常的处理 *@Author:杨攀 *@Since: 2018年6月26日下午3:28:14 *@param e *@return */ @ExceptionHandler(value= Exception.class) @ResponseBody public ResultBean defultExcepitonHandler(Exception e) { //打印错误日志 logger.error ("----Errcode:{}, Message:{}", ErrorEnum.ERROR.getErrcode (), e.getMessage ()); return ResultUtil.error (ErrorEnum.ERROR.getErrcode (), e.getMessage (), null); } /** *@Description: 自定义的业务异常处理 *@Author:杨攀 *@Since: 2018年6月26日下午3:28:23 *@param e *@return */ @ExceptionHandler(value= BusinessException.class) @ResponseBody public ResultBean businessExceptionHandler(BusinessException e) { //打印错误日志 logger.error ("----Errcode:{}, Message:{}", e.getErrcode (), e.getMessage ()); return ResultUtil.error (e.getErrcode (), e.getMessage (), null); } }
我们的自定义异常(业务异常) BusinessException.java
package com.topinfo.common.exception; /** *@Description: 业务异常 *@Author:杨攀 *@Since:2018年6月26日下午3:20:45 */ public class BusinessException extends RuntimeException{ private Integer errcode; private String message; public BusinessException(ErrorEnum errorEnum) { super(errorEnum.getErrmsg ()); this.errcode = errorEnum.getErrcode (); this.message = errorEnum.getErrmsg (); } public BusinessException(Integer errcode, String message) { this.errcode = errcode; this.message = message; } public Integer getErrcode(){ return errcode; } public String getMessage(){ return message; } }
统一类型管理: 定义异常的枚举 ErrorEnum.java
package com.topinfo.common.exception; public enum ErrorEnum { ERROR (-1, "错误"), PARAM_ERROR(-100,"参数错误"), USER_NOTEXIST(-200, "用户不存在"); //...继续添加其他异常 private Integer errcode; private String errmsg; private ErrorEnum(Integer errcode, String errmsg) { this.errcode = errcode; this.errmsg = errmsg; } public Integer getErrcode(){ return errcode; } public String getErrmsg(){ return errmsg; } }
统一结果的返回Json: ResultBean.java
package com.topinfo.common.exception; import java.util.List; /** *@Description: 成功实体 *@Author:杨攀 *@Since:2018年6月26日下午2:38:39 */ public class ResultBean { /** *@Fields 成功返回: 1 失败返回:-1 或 其他错误码 */ private Integer errcode; /** *@Fields 返回成功或失败的信息提示. */ private String message; /** *@Fields 返回的数据,不管是单个对象还是对个对象,统一放到List中. */ private List<Object> data; /** *@Fields 分页的时候,返回总共有多少条记录数. */ private Long total; public Integer getErrcode(){ return errcode; } public void setErrcode(Integer errcode){ this.errcode = errcode; } public String getMessage(){ return message; } public void setMessage(String message){ this.message = message; } public List<Object> getData(){ return data; } public void setData(List<Object> data){ this.data = data; } public Long getTotal(){ return total; } public void setTotal(Long total){ this.total = total; } }
定义返回结果的 工具类:
package com.topinfo.common.utils; import java.util.ArrayList; import java.util.List; import com.topinfo.common.exception.ResultBean; /** *@Description: 返回的工具类 *@Author:杨攀 *@Since:2018年6月26日下午3:07:06 */ public class ResultUtil { /** *@Description: 成功返回 *@Author:杨攀 *@Since: 2018年6月26日下午3:00:41 *@param object 数据 *@return */ public static ResultBean success(Object object){ ResultBean bean = new ResultBean(); bean.setErrcode (1); bean.setMessage ("成功"); List<Object> data = new ArrayList<Object> (); data.add (object); bean.setData (data); return bean; } /** *@Description: 成功返回 *@Author:杨攀 *@Since: 2018年6月26日下午3:01:25 *@param data 数据 *@return */ public static ResultBean success(List<Object> data){ ResultBean bean = new ResultBean(); bean.setErrcode (1); bean.setMessage ("成功"); if(data != null){ bean.setData (data); } return bean; } /** *@Description: 错误返回 *@Author:杨攀 *@Since: 2018年6月26日下午3:04:27 *@param errcode 错误码 *@param message 错误消息 *@param object 数据 *@return */ public static ResultBean error(Integer errcode, String message, Object object){ ResultBean bean = new ResultBean(); bean.setErrcode (errcode); bean.setMessage (message); if(object != null){ List<Object> data = new ArrayList<Object> (); data.add (object); bean.setData (data); } return bean; } /** *@Description: 错误返回 *@Author:杨攀 *@Since: 2018年6月26日下午3:03:44 *@param errcode 错误码 *@param message 错误消息 *@param data 数据 *@return */ public static ResultBean error(Integer errcode, String message, List<Object> data){ ResultBean bean = new ResultBean(); bean.setErrcode (errcode); bean.setMessage (message); if(data != null){ bean.setData (data); } return bean; } }
四:日志的配置
使用 yml 和 xml 配置:
logback-boot.xml:
<configuration> <!-- 用来定义变量值的标签 --> <property name="logging.dir" value="d:/upload"/> <!-- %m输出的信息,%p日志级别,%t线程名,%d日期,%c类的全名,%i索引【从数字0开始递增】,,, --> <!-- appender是configuration的子节点,是负责写日志的组件。 --> <!-- ConsoleAppender:把日志输出到控制台 --> <appender name="Console" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n </pattern> <!-- 控制台也要使用UTF-8,不要使用GBK,否则会中文乱码 --> <charset>UTF-8</charset> </encoder> </appender> <!-- RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 --> <!-- 以下的大概意思是:1.先按日期存日志,日期变了,将前一天的日志文件名重命名为XXX%日期%索引,新的日志仍然是topinfo.log --> <!-- 2.如果日期没有发生变化,但是当前日志的文件大小超过1KB时,对当前日志进行分割 重命名 --> <appender name="LogFile" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- rollingPolicy:当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名。 --> <!-- TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 活动文件的名字会根据fileNamePattern的值,每隔一段时间改变一次 --> <!-- 文件名:log/topinfo.2018-06-26.0.log --> <fileNamePattern>${logging.dir}/topinfo.%d.%i.log</fileNamePattern> <!-- 每产生一个日志文件,该日志文件的保存期限为30天 --> <maxHistory>30</maxHistory> <!-- maxFileSize:这是活动文件的大小,默认值是10MB --> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>20MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> </rollingPolicy> <encoder> <!-- pattern节点,用来设置日志的输入格式 --> <pattern> %d %p (%file:%line\)- %m%n </pattern> <!-- 记录日志的编码 --> <charset>UTF-8</charset> <!-- 此处设置字符集 --> </encoder> </appender> <!-- 配置日志文件输出 Error 级别 --> <appender name="LogErrorFile" class="ch.qos.logback.core.rolling.RollingFileAppender"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>ERROR</level> </filter> <!-- rollingPolicy:当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名。 --> <!-- TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 活动文件的名字会根据fileNamePattern的值,每隔一段时间改变一次 --> <!-- 文件名:log/topinfo.2018-06-26.0.log --> <fileNamePattern>${logging.dir}/topinfo-error.%d.%i.log</fileNamePattern> <!-- 每产生一个日志文件,该日志文件的保存期限为30天 --> <maxHistory>30</maxHistory> <!-- maxFileSize:这是活动文件的大小,默认值是10MB --> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>20MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> </rollingPolicy> <encoder> <!-- pattern节点,用来设置日志的输入格式 --> <pattern> %d %p (%file:%line\)- %m%n </pattern> <!-- 记录日志的编码 --> <charset>UTF-8</charset> <!-- 此处设置字符集 --> </encoder> </appender> <!-- 控制台输出日志级别: 级别依次为【从高到低】:FATAL > ERROR > WARN > INFO > DEBUG --> <root level="${logging.level}"> <appender-ref ref="Console" /> <appender-ref ref="LogFile" /> <appender-ref ref="LogErrorFile" /> </root> <!-- 指定项目中某个包,当有日志操作行为时的日志记录级别 --> <!-- com.topinfo为根包,也就是只要是发生在这个根包下面的所有日志操作行为的权限都是DEBUG --> <!-- 级别依次为【从高到低】:FATAL > ERROR > WARN > INFO > DEBUG > TRACE --> <!-- <logger name="com.topinfo" level="${logging.level}"> <appender-ref ref="LogFile" /> </logger> --> </configuration>
在 yml 中配置日志的级别
这里有个问题: yml 中设置日志的路径一直无效,实在是不知道问题出来哪里,暂时只能在 xml中配置。 如果知道如何配置的,请给我留言。
#logging 日志配置 logging: # 日志配置文件,Spring Boot默认使用classpath路径下的日志配置文件 config: classpath:logback-boot.xml # 日志文件,绝对路径或相对路径 D:/upload #file: # 在这里设置无效,请到 上面的logback-boot.xml 中设置 # 日志输出级别: 级别依次为【从高到低】:FATAL > ERROR > WARN > INFO > DEBUG level: root: INFO # 配置spring web日志级别
运行测试:
好了, 所有的工作就完成了。 感觉是不是好了很多其他配置。 总体来说,还是很清爽的。