最近从andriod转型后端开发,比较忙,所以有半年左右没有更新了,望大家见谅~
今天我就来讲一讲spring webMVC+msql的配置相关问题讲解
接口开发其实及其简单,任何java基础不错的同学,看了下面的文章分分钟就能写一个接口
准备工作如下:
开发工具:IDEA (注册码,http://idea.lanyus.com/)
数据库:MySQL(8.0版本的认证方式和与之前的版本发生了变化,时区也需要设置为GMT+8)
运行环境:tomcat(8.0+,9.0+都可以)
仓库:maven(maven3.0+)
http模拟:postman
有涉及开发环境问题,请留言
涉及到的第三方开源框架:spring、javax.validation、org.hibernate.validator、mybatis、mybatis-spring、mysql-connector-java、commons-dbcp2、fastjson、aspectjweaver、spring-jdbc
大家不用看到这么多的第三方框架就怕,其实它们各有各的关键特性,如果不想实现这些特性也可以不用引入。
spring:全局管理配置,依赖注入,aop等(整个项目的基础),具体请自行查阅资料,没有看过的童鞋也可以继续往下看一些基础设置,不难理解。
javax.validation:这个包主要是对参数进行验证并进行全局管理(具体用于接口入参判空、长度限制等)
org.hibernate.validator:这个包是具体实现,需配合javax.validation的出现
mybatis:简化数据库操作的类
mybatis-spring:mybatis的生命周期交由spring管理
mysql-connector-java:mysql的连接认证等,连接数据库必须,必须和pc的mysql版本对应,因为8.0认证方式发生了变更,如果使用低版本是无法连接到8.0+的数据库的(踩坑1)
spring-jdbc:数据库连接必须(踩坑2,一定要记得引入)
commons-dbcp2:数据源,有多种类型,这里选用dbcp2,
fastjson:处理json数据
aspectjweaver:aop必须的包
接下来看看项目的具体结构
req:request简写,主要存放接口请求参数相关类
rsp:response简写,主要存放返回参数相关类
CException:自定义的异常处理类
User:用户信息,字段名和数据库的字段名对应
controll包下存放所有的controller
ErrorController:用于处理全局异常
UserController:用户处理用户相关的控制
dao包:此包下的类负责提供数据,并操作数据库
mybatis包:此包下的接口主要用于和mapper文件映射,把sql操作的结果处理为标准的java结构
service包:控制器捕捉到url路径后,业务逻辑交由service层处理
resource/mybation/*.xml:此资源文件与java目录下的mybatis接口文件对应,支持sql语句,
resource/spring/application-context.xml:spring初始化的入口,在这完成初始化的配置
resource/spring/msql-context.xml:数据的配置文件(如数据库地址,用户名密码等)
webapp/WEB-INF/web.xml 可以理解为程序的入口,tomcat启动时读取的第一个配置文件
万事开头难,在看具体代码前,先梳理一下流程?第一,入口函数在哪?
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/application-context.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>servlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>servlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
首先,先看一下目录结构中的webapp/WEB-INF/web.xml,这是tomcat启动时,读取的第一个配置文件,这就是我们整个程序的入口,下面就看看这个文件干了什么事情。
不难看出这是一个xml格式的文件,其中有4个节点,listener节点标识了一个监听事件,这里我们是用spring框架提供的类,这个类做的事情就是初始化context-param节点下的参数,这里我们的目的就是初始化spring。
servlet节点也定义了一个spring框架提供的servlet类,它的作用是拦截所有url请求,并分发到相应的控制器,
servlet-mapping节点中的url-pattern用于匹配地址,/ 表示匹配所有地址,通过servlet-name和servlet节点关联。
第二步,我们看看spring的初始化都干了什么事,路径如下,resource/spring/mc-application-context.xml,
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns="http://www.springframework.org/schema/beans"
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.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
// 扫描指定包的类
<context:component-scan base-package="com.ccy.demo"/>
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
// 设置 控制器 支持 application/json格式的数据,默认是表单提交
<property name="supportedMediaTypes">
<list>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<import resource="mysql-context.xml"></import>
component-scan节点,作用是扫描指定包下的所有类,把@Controller @Service @Repository @Component 注解的类加入spring管理。
tips:这里补充一下这4个注解的说明,从功能来说,这4个注解是一样的,唯一的区别就在于对代码逻辑解耦,按照规范,我们在Controller层不做任何逻辑处理,只用来表示接口的入口,在Service中处理逻辑代码,在Repository层中处理数据的存取(简单来说就是和数据库的交互)。 这样任何一个service都可以从多个Repository中获取数据,实现的代码的解耦,Component用于标注功能不明确的类。简单来说这4个注解其实都可以用Component代替。另一个好处就是如果按照规范注解,当想要修改逻辑代码时就去service层寻找,想修改数据时就去Respoistory层寻找,达到快速定位的目的。
import resource节点,作用是导入其他指定的配置文件,这里我们导入了数据库的配置文件。
第三步,数据库配置
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
// 数据源配置,记得导入相应的包,否则tomcat或提示配置文件有错
<bean id="datasource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb?serverTimezone=GMT%2B8"/>
<property name="username" value="root"/>
<property name="password" value="xsm520@ccy"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="datasource"/>
<property name="mapperLocations" value="classpath:mybatis/*.xml"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.ccy.demo.dao.mybatis"/>
</bean>
有3个节点,
dateSource,指定数据源,指定数据库地址,指定用户名和密码。
sqlSession,指定mapper文件的位置,
mappeConfig,用于关联java文件和mapper文件的映射关系
这一步现在没看懂没关系,后面会具体讲解
流程清楚了,接下来就可以撸一撸代码了,大家可以想一想写接口的第一步应该是做什么操作,肯定是需要提供一个url地址让用户能访问,并能传递一些参数进入。
我们假设现在提供一个接口,让用户传入自己的基本信息,并保存到数据库,具体步骤如下
1.提供一个url地址让用户访问,如http://localhost/api/user/add,其中http://localhost/当发布到公网时,就是域名或ip,本地调试时,一般是http://localhost:8080/springweb_war_exploded/,有可能不同环境不一样,具体后面讲解。所以我们应该提供的完整访问地址是http://localhost:8080/springweb_war_exploded/api/user/add
2.提供一个add方法,此方法带有一个user的实体,包含用户基本信息
3.连接数据库
4.存入数据库
第一步,
@Controller
@RequestMapping(value = "api/user")
public class UserController {
// spring注入
@Autowired
private UserService mUserService;
@RequestMapping(value = "/add", method = RequestMethod.POST)
@ResponseBody
public AddUserRsp add(@RequestBody @Valid AddUserReq req) throws CException {
return mUserService.add(req);
}
@RequestMapping(value = "/getAllUser", method = RequestMethod.POST)
@ResponseBody
public GetAllUserRsp getAllUser() throws CException {
return mUserService.getAllUser();
}
@RequestMapping(value = "/getUser", method = RequestMethod.POST)
@ResponseBody
public GetUserRsp getUser(@RequestBody @Valid GetUserReq req) throws CException {
return mUserService.getUser(req);
}
}
@Controller
@RequestMapping(value = “api/user”)
这2个注解表示了访问路径如下:api/user,从字面意思理解,并且这个类是一个controller
我们提供一个add方法,并提供一个user的入参,方法标注下一级路径/add,并设置访问方式为post,注意注解@ResponseBody和@RequestBody,某些情况下是可以缺省的,不过第一次,为了避免遇到奇怪的坑,建议加上,这里我们第一步就完成了。
这里我们的controller只做了一件事,接受到对应的请求后,把事情交由service层处理
第二部,接下来就进行逻辑操作,取出user中的姓名和年龄判断
注意这里的参数判断,如果每个接口都这样判断明显的会造成代码臃肿,这时候可以使用我刚刚提到的validation包,用于参数验证,修改后的代码如下:
package com.ccy.demo.bena.req;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
public class AddUserReq {
@NotNull(message = "name不能为空")
private String name;
@NotNull(message = "age不能为空")
@Min(0)
@Max(150)
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
在user中的字段加上注解,表示name不能为null,并且age的范围应该在0-150之间,并在方法参数前加上注解@Valid,表示启用
第三步,连接数据库,首先确保电脑上已经安装mysql,并已经创建好数据库和表,确保已经引入相关包,数据库的连接信息已经在配置文件中申明了,如果能正常启动tomcat就表示已经连接成功
第四步,通过mybatis操作数据库,代码在dao/mybatis/UserBatis ,这个接口定义你想要的数据结构和查询参数,
spring会找到对应的mepper文件 执行sql语句
package com.ccy.demo.dao.mybatis;
import com.ccy.demo.bena.table.UserTo;
import org.apache.ibatis.annotations.Param;
import java.util.List;
// 可以理解为把数据库查询的数据转换为java结构的集合
public interface UserBatis {
// 注意注解@Param
void addUser(@Param("name") String name, @Param("age") int age) throws Exception;
List<UserTo> getAll() throws Exception;
UserTo getUser(@Param("name") String name) throws Exception;
}
与之对应的mapeer文件,注意id要与java文件的方法名一致,如果有返回数据需定义resultType,变量用#{key,type}表示,注意需与数据库定义的字段名保持一致
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
// 对应的java文件
<mapper namespace="com.ccy.demo.dao.mybatis.UserBatis">
<insert id="addUser">
INSERT INTO t_users (`name`, age)
VALUES (
#{name, jdbcType=VARCHAR},
#{age, jdbcType=INTEGER}
)
</insert>
<select id="getAll" resultType="com.ccy.demo.bena.table.UserTo">
select `name`,age from t_users
</select>
<select id="getUser" resultType="com.ccy.demo.bena.table.UserTo">
select `name`,age from t_users where `name` = #{name, jdbcType=VARCHAR}
</select>
第五步,启动tomcat,用postman验证接口是否可以访问
插入用户
查询全部用户
查询单个用户
至此成功~
补充:全局异常捕获
ErrorController是用来捕获全局异常的,看看这个类都干了什么
@ControllerAdvice
public class ErrorController {
/**
* Exception异常处理,表示系统错误
*
* @param ex
*/
@ResponseBody
@ExceptionHandler(value = Exception.class)
public ErrorRsp errHandler(Exception ex) {
ErrorRsp rsp = new ErrorRsp();
rsp.setErrorCode(ErrCodeUtils.SYSTEM_ERROR);
rsp.setErrorMsg("system error");
return rsp;
}
/**
* CException异常处理,表示内部错误
*
* @param ex
*/
@ResponseBody
@ExceptionHandler(value = CException.class)
public ErrorRsp errHandler(CException ex) {
ErrorRsp rsp = new ErrorRsp();
rsp.setErrorCode(ErrCodeUtils.INTERNAL_ERROR);
rsp.setErrorMsg("internal error");
return rsp;
}
/**
* MethodArgumentNotValidException异常处理,参数错误
*
* @param ex
* @return
*/
@ResponseBody
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public ErrorRsp errHandler(MethodArgumentNotValidException ex) {
StringBuilder stringBuilder = new StringBuilder();
List<ObjectError> objectErrors = ex.getBindingResult().getAllErrors();
for (ObjectError error : objectErrors) {
stringBuilder.append(error.getDefaultMessage());
stringBuilder.append(",");
}
ErrorRsp rsp = new ErrorRsp();
rsp.setErrorCode(ErrCodeUtils.PARAM_ERROR);
rsp.setErrorMsg(stringBuilder.toString());
return rsp;
}
}
看一下MethodArgumentNotValidException这个异常 就是上面提到的检验参数的异常,大家可以试一下如果在add接口传入错误参数,接口是怎么返回的
我后面完善后会把项目上传至github,敬请期待~