1后台api
知识点:spring核心,springMVC,MyBatis
SSM整合实质:
service中要访问到mapper,要求mapper代理的对象要交给spring容器;
mybatis的事务管理能力弱,事务管理交给spring
搭建的目标是 前端可以访问 controller,且controller返回json
增加jar依赖
<!--增加springMVC的依赖-->
<!-- https://mvnrepository.com/artifact/org.springframework/springwebmvc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!--json支持-->
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind ->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.0</version>
</dependency>
配置web.xml
<!--编码字符集过滤器-->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filterclass>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
</filter>
<!--开启方法过滤器-->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filterclass>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--前端控制器-->
<servlet>
<servlet-name>springMVC</servlet-name> <servletclass>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--告诉DispatcherServlet 配置文件路径-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springMVC.xml</param-value>
</init-param>
<!--随着服务器的启动而启动springMVC容器-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
配置springMVC.xml
<!--开启扫描-->
<context:component-scan base-package="com.oracle" use-defaultfilters="false" >
<!--扫描bean设置类型,避免和spring核心容器(父容器重复扫描,解决事务不起作用)--> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
<!--开启容器静态资源访问-->
<mvc:default-servlet-handler/>
<!--开启了容器默认servlet,处理静态资源,会导致动态资源无法访问-->
<!--json,校验,类型转换,异常处理..-->
<!--要使用springMVC,就开启-->
<mvc:annotation-driven/>
编写controller
@RestController @RequestMapping("account")
public class AccountController {
@GetMapping("hello")
public Map<String, Object> hello() {
Map<String, Object> map = new HashMap<>();
map.put("success", true);
map.put("datas", "springMVC已经可以正常访问,AccountController Restful风格 API向你问好");
return map;
}
}
applicationContext.xml
<!--扫描-->
<context:component-scan base-package="com.oracle" >
<!--排除controller注解-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
配置web.xml
<!--设置监听器读取配置文件参数-->
<context-param>
<param-name>contextConfigLocation</param-name>
<!- <paramvalue>classpath:applicationContext.xml,classpath:applicationContext-aop.xml... </param-value> -->
<param-value>classpath:applicationContext*.xml</param-value>
</context-param>
<!--使用监听器启动spring核心容器-->
<listener> <listenerclass>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
jar包导入
<!--aop-->
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
编写切面类,交给spring容器
@Component
@Aspect//注解本身没有意义,是注解解析器使得它有意义
public class LogAspect {
private Logger log=Logger.getLogger(LogAspect.class);
//切点
//这个配置内容很丰富,我们使用了比较常用的 简单配置
@Pointcut("execution(* com.oracle..*.*(..))")
public void logPointCut(){}
//通知
@Around(value = "logPointCut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long stime =System.currentTimeMillis();
Object obj =pjp.proceed(pjp.getArgs());
long etime =System.currentTimeMillis();
String cname = pjp.getTarget().getClass().getName();
String mname =pjp.getSignature().getName();
String msg = String.format("%s类%s方法执行耗时%s毫秒",cname,mname,(etime-stime));
log.debug(msg);
// System.out.println(msg);
return obj;
}
}
aop.xml
<!--开启注解解析器-->
<!--让spring自动选择是 jdk 还是 cglib代理-->
<aop:aspectj-autoproxy />
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.22</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
applicationContext-mybatis.xml
<!--加载db.properties文件-->
<context:property-placeholder location="classpath:db.properties"/>
<!--配置数据源-->
<bean id="dataSource" init-method="init" destroy-method="close" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
<property name="url" value="${db.url}"/>
<property name="driverClassName" value="${db.driver}"/>
<!--现阶段 数据库连接池参数 默认-->
</bean>
<!--mybatis和spring整合-->
<!--将sqlsessionfactory交给spring创建-->
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--数据源-->
<property name="dataSource" ref="dataSource"/>
<!--加载mybatis配置文件-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!--设置model的别名-->
<property name="typeAliasesPackage" value="com.oracle.model"/>
</bean>
<!--将mapper接口交给spring容器-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.oracle.mapper"/>
</bean>
maven打包
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
使用插件生成
model,mapper 接口,mapper.xml
测试
通过controller调用service,调用mapper,前端可以看到数据库表所有数据
明确事务是在service层开启的,在ssm整合的时候,尽量使用xml配置
applicationContext-tx.xml
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--指定对哪一个数据源进行事务的管理-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--事务通知器-->
<!--配置事务规则-->
<!--transaction-manager 指定事务管理器,默认查找transactionManager-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--事务规则,具体方法的事务属性-->
<tx:attributes>
<!--对哪些方法进行事务的处理-->
<!--rollback-for="java.io.FileNotFoundException"-->
<!--对于查询的操作使用只读事务,提升性能-->
<tx:method name="query*" read-only="true" propagation="SUPPORTS"/>
<tx:method name="load*" read-only="true" propagation="SUPPORTS"/>
<tx:method name="select*" read-only="true" propagation="SUPPORTS"/>
<tx:method name="get*" read-only="true" propagation="SUPPORTS"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!--配置aop-->
<aop:config>
<!--定义通知器,就是将 切入点与事务通知进行绑定-->
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.oracle.service..*.*(..))"></aop:advisor>
</aop:config>
配置二级缓存
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-memcached</artifactId>
<version>1.0.0</version>
</dependency>
加到mapper.xml
memcached.properties
#any string identifier
org.mybatis.caches.memcached.keyprefix=_biz-cache-wk_
#space separated list of ${host}:${port}
org.mybatis.caches.memcached.servers=127.0.0.1:11211
#org.mybatis.caches.memcached.servers=192.168.0.44:12000
#Any class that implementsnet.spy.memcached.ConnectionFactory
org.mybatis.caches.memcached.connectionfactory=net.spy.memcached.DefaultConnectionFactory
#the number of seconds in 30 days the expiration time (in seconds)
org.mybatis.caches.memcached.expiration=6000
#flag to enable/disable the async get
org.mybatis.caches.memcached.asyncget=false
#the timeout when using async get
org.mybatis.caches.memcached.timeout=5
#the timeout unit when using async get
org.mybatis.caches.memcached.timeoutunit=java.util.concurrent.TimeUnit.SECONDS
#if true, objects will be GZIP compressed before putting them to
org.mybatis.caches.memcached.compression=false
#\u7f13\u5b58\u670d\u52a1\u5668\u5b95\u673a\u540e\u591a\u4e45\u4e0d\u4f7f\u7528memcached \u6beb\u79d2\u4e3a\u5355\u4f4d
#refuse time when connection refused
org.mybatis.caches.memcached.refuseperiod=1000
应提前开启memcached
分页插件
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper --> <dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.11</version>
</dependency>
mybatis-config.xml
<!--mybatis配置根节点-->
<configuration>
<!--全局设置-->
<settings>
<!--一级缓存设置-->
<!--默认已经设置好了-->
<setting name="localCacheScope" value="STATEMENT"/>
<!--开启二级缓存 默认就是开启的-->
<setting name="cacheEnabled" value="true"/>
<!--为mybatis指定日志记录-->
<setting name="logImpl" value="LOG4J"/>
</settings>
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 使用下面的方式配置参数,后面会有所有的参数介绍 -->
<!-- <property name="param1" value="value1"/>-->
</plugin>
</plugins>
</configuration>
service
public PageInfo<Account> queryAll(Integer pageNum, Integer pageSize) {
if (pageNum == null) {
pageNum = 1;
}
if (pageSize == null) {
pageSize = 5;//尽量写在配置文件中,用@value 注入
}
PageHelper.startPage(pageNum, pageSize);
List<Account> result = accountMapper.select();
PageInfo<Account> pageinfo = new PageInfo<Account>(result);
return pageinfo;
}
着重说明controller
//此时query接口,要支持全查和按照条件查两种方式
@GetMapping(value = {"account/{pageNum}/{pageSize}", "account"})
public AjaxResult query(@PathVariable(value = "pageNum", required = false) Integer pageNum,
@PathVariable(value = "pageSize", required = false) Integer pageSize) {
AjaxResult result = new AjaxResult();
result.setDatas(accountService.queryAll(pageNum, pageSize));
return result;
}
controller中要提供其余的接口,在springMVC中增加JSR303校验,优化代码配置异常处理,还可以增 加拦截器。
增加 新增,修改 接口 增加hibernate-validator maven依赖
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-validator --> <dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.0.Final</version>
</dependency>
编写保存查询修改删除数据接口
//此时query接口,通过id查询单条数据
@GetMapping(value = "account/{aid}")
public AjaxResult queryById(@PathVariable(value = "aid") Integer aid) {
AjaxResult result = new AjaxResult();
result.setDatas(accountService.queryById(aid));
return result;
}
@DeleteMapping("account/{id}")
public AjaxResult delete(@PathVariable("id") Integer id) {
AjaxResult result = new AjaxResult();
accountService.deleteById(id);
result.setDatas("已经成功删除");
return result;
}
//新增方法
//对参数进行校验,校验分组
//对数据库非空约束字段 进行校验
@PostMapping("account")
@ApiOperation(value = "用户添加")
public AjaxResult save(@Validated(value = {AccountSaveValidator.class}) Account account, BindingResult errors) throws BindException {
AjaxResult result = new AjaxResult();
//调用service,还要处理异常,现在先测试校验规则
if (errors.hasErrors()) {
throw new BindException(errors);
}
accountService.save(account);
result.setDatas("添加用户成功");
return result;
}
//修改方法
@PutMapping("account")
//修改的时候,判断密码的长度至少是8位
//注意增加 id 必须传递的这个校验
//还要注意 插件生成的代码 a_nikeName 和属性 aNikename setaNikename, 最好数据库列没有 _ ,实体类修改
//@Validated(AccountUpdateValidator.class)
public AjaxResult update(@RequestBody Account account, BindingResult errors) throws BindException {
AjaxResult result = new AjaxResult();
//调用service,还要处理异常,现在先测试校验规则
if (errors.hasErrors()) {
throw new BindException(errors);
}
System.out.println("修改的生日为-->"+account.getAbirthday());
accountService.update(account);
result.setDatas("修改用户信息成功");
return result;
}
模型中增加相关的校验注解,并指定分组
/**
* 主键编号
*/
private Integer aid;
/**
* 登陆用户名
*/
// @ApiModelProperty(name="用户登陆名称" )
@NotNull(message = "登陆用户名必须传递",groups = {AccountSaveValidator.class})
@NotBlank(message = "登陆用户名不能为空,空字符串也不行",groups = {AccountSaveValidator.class})
private String aname;
/**
* 密码
*/
// @ApiModelProperty(name="用户登陆密码" )
@NotNull(message = "登陆密码必须传递",groups = {AccountSaveValidator.class})
@NotBlank(message = "登陆密码不能为空,空字符串也不行",groups = {AccountSaveValidator.class})
@Length(min=8,max=11,groups = {AccountUpdateValidator.class})
private String apass;
/**
* 昵称
*/
// @ApiModelProperty(name="用户昵称" ,notes = )
private String anikename;
/**
* 生日
*/
@JsonFormat(pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date abirthday;
private static final long serialVersionUID = 1L;
使用异常处理
编写全局异常处理类,可以细化到对不同类型的异常进行个性化的处理
/标注这是一个异常处理类
@ControllerAdvice
public class OracleSpringMVCException {
private Logger logger=Logger.getLogger(OracleSpringMVCException.class);
//用于处理参数校验失败异常
@ExceptionHandler(BindException.class)
@ResponseBody
public AjaxResult handlerBindException(BindException e) {
AjaxResult result = new AjaxResult();
result.setSuccess(false);
BindingResult errors = e.getBindingResult();
Map<String, String> map = new HashMap<>();
List<FieldError> fieldErrors = errors.getFieldErrors();
for (FieldError fieldError : fieldErrors) {
String fname = fieldError.getField();
String msg = fieldError.getDefaultMessage();
map.put(fname, msg);
}
result.setSuccess(false);
result.setMsg(map);
return result;
}
@ExceptionHandler(Exception.class)
@ResponseBody
public AjaxResult handlerException(Exception e) {
AjaxResult result = new AjaxResult();
result.setSuccess(false);
result.setMsg("服务器繁忙中,请稍后再试");
//一定要将有价值的日志储存,运维人员及时发现问题
logger.error(e.getMessage());
e.printStackTrace();
return result;
}
}
增加拦截器
springMVC中的拦截器,就是拦截controller方法的。和filter 区分一下
创建拦截器
/**
* 编写日志拦截器
*/
public class OracleSpringMVCLogInterceptor implements HandlerInterceptor {
private Logger logger=Logger.getLogger(OracleSpringMVCLogInterceptor.class);
@Override
public boolean preHandle(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response, Object handler) throws Exception {
//服务器下发,允许跨域提交
//
HttpServletResponse res = (HttpServletResponse) response;
res.addHeader("Access-Control-Allow-Credentials", "true");
res.addHeader("Access-Control-Allow-Origin", "*");
// res.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
res.addHeader("Access-Control-Allow-Methods", "*");
// res.addHeader("Access-Control-Allow-Headers", "Content-Type,X-CAFAuthorization-Token,sessionToken,X-TOKEN");
long stime = System.currentTimeMillis();
request.setAttribute("stime", stime);
return true;//result false就不向下执行了,到不了controller
}
@Override
public void afterCompletion(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response, Object handler, Exception ex) throws Exception {
long etime = System.currentTimeMillis();
long stime = Long.parseLong(request.getAttribute("stime").toString());
long time = etime - stime;
StringBuffer requestURL = request.getRequestURL();
String msg =String.format("%s资源,处理时间%s毫秒",requestURL,time);
// System.out.println(msg);
logger.debug(msg);
}
}
配置拦截器
xml,annotation
<!--配置拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.oracle.springMVC.interceptor.OracleSpringMVCLogInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
springMVC整合swagger2
添加maven依赖
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 --> <dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swaggerui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
配置
将swagger交给spring容器 ,配置
@Component
@Configuration
@EnableSwagger2
@EnableWebMvc
@ComponentScan("com.oracle.controller")
public class SwaggerConfig {
@Bean
public Docket createAPI() {
return new Docket(DocumentationType.SWAGGER_2).forCodeGeneration(true).select().apis(RequestHandlerSelectors.any())
//过滤生成链接
.paths(PathSelectors.any()).build().apiInfo(apiInfo());
}
private ApiInfo apiInfo() {
Contact contact = new Contact("xk", "http://www.oracle.com.cn", "029xk@163.com");
ApiInfo apiInfo = new ApiInfoBuilder().license("Apache License Version 2.0").title("SSM整合接口文档").description("Swagger API Teste").contact(contact).version("1.0").build();
return apiInfo;
}
}
/swagger-ui.html
2 前端
基于boostrap+jquery 工具:Hbuilder
var apiServer = "http://localhost:8080/";
//弹窗保存添加数据按钮
$(function() {
//加载数据
initData();
$("#btn_open_add").click(function() {
console.log("来了");
//取值
//from表单取值 jquery方法
//lname lpass lrpass lnikename lbirthday
var datas = {
aname: $("#lname").val(),
apass: $("#lpass").val(),
rpass: $("#lrpass").val(),
anikename: $("#lnikename").val(),
abirthday: $("#lbirthday").val()
}
//校验,进行前端校验
//我们省略了前端校验
//发送ajax
$.ajax({
type:"post",
url: apiServer+"api/account",
data:datas,
success:function(result){
if(result.success){
$("#ajaxMsg").text(result.datas);
$('#myAddModal').modal('hide');
setTimeout(function() {
$("#ajaxMsg").text("");
}, 2000);
}else{
$("#myAddModal_div_msg").text(JSON.stringify(result.msg));
}
}
});
});
$("#btn_open_update").click(function() {
//取得修改的数据
//目前后台接口是以id作为修改条件
var datas = {
aname: $("#ulname").val(),
aid: $("#uaid").val(),
apass: $("#ulpass").val(),
rpass: $("#ulrpass").val(),
anikename: $("#ulnikename").val(),
abirthday: $("#ulbirthday").val()
}
$.ajax({
type:"put",
url: apiServer+"api/account",
data:JSON.stringify(datas),
contentType:"application/json",
success:function(result){
//{"success":false,"datas":null,"msg":"服务器繁忙中,请稍后再试"}
if(result.success){
$("#ajaxMsg").text(result.datas);
//重新加载数据
initData();
$('#myUpdateModal').modal('hide');
setTimeout(function() {
$("#ajaxMsg").text("");
}, 2000);
}else{
//显示错误的消息
$("#myUpdateModal_div_msg").text(result.msg);
}
}
});
});
});
//加载数据
function initData() {
//先清空表格数据
//$("tbody").empty();
//发送ajax解析数据
$("#ajaxMsg").text("开始加载数据,请稍后");
$.ajax({
url: apiServer + "api/account/1/3",
type: "get",
datas:{time:new Date()},
dataType: "json",
success: function(result) {
$("#tobodyDatas").empty(); //先清空表格数据
console.log(result);
if (result.success) {
var list = result.datas.list;
$(list).each(function(index, item) {
console.log(item.anikename == null ? ' ' : item.anikename);
var $tr = "<tr>";
$tr += "<td>" + item.aid + "</td>";
$tr += "<td>" + item.aname + "</td>";
$tr += "<td>" + (item.anikename == null ? '' : item.anikename) + "</td>";
$tr += "<td>" + (item.abirthday == null ? '' : item.abirthday) + "</td>";
$tr += "<td>";
$tr += " <a class='a_data_update' href='#'>修改</a>";
$tr += " <a class='a_data_delete' href='#'>删除</a>";
$tr += "</td>";
$tr += "</tr>";
$("#tobodyDatas").append($tr);
});
addOperatorEvent(); //增加事件
$("#ajaxMsg").text("数据加载完毕");
setTimeout(function() {
$("#ajaxMsg").text("");
}, 600);
}
}
});
}
//增加操作事件
//为表格行的 编辑和删除 链接做事件处理
function addOperatorEvent() {
console.log($(".a_data_delete").length);
//$(".a_data_delete").html("<h3>delete</h3>")
//删除数据
$(".a_data_delete").click(function() {
//删除
//取得要删除的id值
var tr = $(this).parent().parent();
var aid=tr.find("td")[0].innerText;
if(window.confirm('请确认是否删除ID:'+aid)){
//发送ajax
$.ajax({
url: apiServer + "api/account/"+aid,
type: "delete",
success: function(result) {
$("#ajaxMsg").text("删除成功");
initData();
setTimeout(function() {
$("#ajaxMsg").text("");
}, 2000);
}
});
//
}
});
//弹出修改数据界面
$(".a_data_update").click(function() {
//修改所需要的数据再前端都有
//不需要从后台查找
//取得当前修改的这一条数据,弹出修改框,赋值.【id]
var tr = $(this).parent().parent();
var tds = tr.find("td");
$("#ulname").val(tds[1].innerText);
$("#ulnikename").val(tds[2].innerText);
$("#ulbirthday").val(tds[3].innerText);
$("#uaid").val(tds[0].innerText);
$("#myUpdateModal").modal('show');
});
}
3前后端整合
发送ajax请求,跨域
1单体应用
2前后端分离
跨域解决问题
springmvc
nginx