面向企业开发基础
一 Maven
1 概要
- 导入依赖
- 项目结构
- 项目后期 —> 测试 —> 打包 —> 部署 —> 多模块开发
2 作用
解决依赖问题
规范项目结构
解决项目后期的打包部署等一些列工作
3 什么是Maven
Maven是使用java编写的开源项目管理工具 POM.xml Project Object Model
为什么需要Maven?
1.项目管理问题:项目中jar包资源越来越多,jar包的管理越来越沉重
2.繁琐:腰围每个项目手动导入所需的jar,需要搜集全部jar
3.复杂:项目中的jar如果需要版本升级,就需要再重新搜集jar
4.冗余:相同的jar在不同的项目中保存了多份
Maven解决了什么问题?
1.添加第三方jar包
2.jar包之间的依赖关系
3.处理jar包之间的冲突
4.获取第三方jar包
5.将项目拆分成多个工程模块
6.实现项目的分布式部署
4 其他构建技术
- Ant 构建 – build.xml
- Maven构建 – pom.xml
- Gradle构建 – build.gradle
5 使用
-
下载maven
-
配置maven
- localRepository
<?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> <!-- 1 修改成你的路径 --> <localRepository>D:\tools\repo</localRepository> <pluginGroups> </pluginGroups> <proxies> </proxies> <servers> <!-- 2 maven 私服 --> </servers> <!-- 3 maven 镜像 --> <mirrors> <mirror> <id>nexus-aliyun</id> <mirrorOf>central</mirrorOf> <name>Nexus aliyun</name> <url>https://maven.aliyun.com/repository/central</url> </mirror> </mirrors> <profiles> </profiles> </settings>
-
用户配置
将配置号的settings.xml文件复制到用户下面的.m2目录下
二 Spring
IoC
IoC:Inversion of Control,控制反转,把创建对象的控制权交给第三方容器
如何将创建对象的控制权交个容器
1.导入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.10</version>
</dependency>
</dependencies>
2.spring-context.xml
<bean id="userDao" class="com.qf.dao.dao.impl.UserDaoImp">
3.从容器中获取创建的类,源码是使用xmlDOM操作通过id获取
public class iocTestMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring-context.xml");
UserDaoImp userDao = classPathXmlApplicationContext.getBean("userDao", UserDaoImp.class);
}
}
//默认查找classpath路径下的文件
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring/spring-ioc.xml");
//多文件,也可传递数组
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring/spring-ioc.xml","spring/spring-ioc2.xml",.....);
//默认为项目工作路径 即项目的根目录
FileSystemXmlApplicationContext applicationContext=
new FileSystemXmlApplicationContext("/src/main/resources/spring/spring-ioc.xml");
//也可以读取classpath下的文件
FileSystemXmlApplicationContext applicationContext=new FileSystemXmlApplicationContext("classpath:spring/spring-ioc.xml");
//使用前缀file 表示的是文件的绝对路径
ApplicationContext applicationContext = new FileSystemXmlApplicationContext("file:D:/app.spring.xml");
//多文件与ClassPathXmlApplicationContext相同
相关源码
if (beanProperty.element("map") != null) {
Map<String, Object> propertiesMap = new HashMap<String, Object>();
Element propertiesListMap = (Element) beanProperty
.elements().get(0);
Iterator<?> propertiesIterator = propertiesListMap
.elements().iterator();
while (propertiesIterator.hasNext()) {
Element vet = (Element) propertiesIterator.next();
if (vet.getName().equals("entry")) {
String key = vet.attributeValue("key");
Iterator<?> valuesIterator = vet.elements()
.iterator();
while (valuesIterator.hasNext()) {
Element value = (Element) valuesIterator.next();
if (value.getName().equals("value")) {
propertiesMap.put(key, value.getText());
}
if (value.getName().equals("ref")) {
propertiesMap.put(key, new String[] { value
.attributeValue("bean") });
}
}
}
}
bean.getProperties().put(name, propertiesMap);
}
public static Object newInstance(String className) {
Class<?> cls = null;
Object obj = null;
try {
cls = Class.forName(className);
obj = cls.newInstance();
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
return obj;
}
DI
DI:Dependency Injection,依赖注入
如何注入?
1.<!--set方法注入
使用的标签:property
标签出现的位置:bean标签的内部
标签中的属性
name:指定注入时所调用的set方法名称
===================
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类数据,在spring的IOC核心容器中出现过的bean对象
优势:
创建对象时没有明确的限制,可以直接使用构造函数
弊端:
如果某个成员必须有值,有可能set方法没有执行赋值
-->
2.<!--构造函数注入:
使用的标签:constructor-arg
标签出现的位置:bean标签的内部
标签中的属性
type:指定要注入的数据的数据类型,该数据类型也是构造函数中的某个参数的类型
index:指定要定索引位置,索引的位置从0开始
name:指定参数名称
=========以上用于指定给构造函数中的哪个参数赋值==========
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类数据,在spring的IOC核心容器中出现过的bean对象
优势:
在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功
弊端:
创建对象时,如果用不到这些数据,也必须提供
-->
<bean id="accountService" class="com.qf.dao.service.impl.AccountServiceImpl"
scope="prototype">
<constructor-arg name="name" value="lisi"></constructor-arg>
<constructor-arg name="age" value="23"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
1.spring-context.xml IoC容器
<bean id="userDao" class="com.qf.dao.dao.impl.UserDaoImp">
</bean>
<bean id="userService" class="com.qf.dao.service.impl.UserServiceImp">
<!-- 注入userDaoImp对象,需要set方法--><!--set注入关键词-->
<property name="userDaoImp" ref="userDao"></property>
</bean>
2.userService声明中多出了一个property的标签,这个标签指向了我们刚才创建的userDao对象,它的作用是把userDao对象传递给userService实现类中的userDaoImp属性,该属性必须拥有set方法才能注入成功,我们把这种往类userService对象中注入其他对象(userDaoImp)的操作称为依赖注入,其中的id或者name必须与userService实现类中变量名称相同,到此我们就完成对需要创建的对象声明。
通过源码得知,对于非集合类型的属性,底层代码大量使用了JDK的反射和内省机制,通过属性的getter方法(reader method)获取指定属性注入以前的值,同时调用属性的setter方法(writer method)为属性设置注入后的值,所以说为什么要在类里面设置set方法。
public class UserServiceImp implements UserService {
//userDaoImp为注入的对象
private UserDaoImp userDaoImp;
//注入userDaoImp对象,需要set方法
public void setUserDaoImp(UserDaoImp userDaoImp) {
this.userDaoImp = userDaoImp;
}
@Override
public Integer login() {
return userDaoImp.insert();
}
}
注册对象
1.xml
2.注解
3.Java Config
1.xml
2.导入依赖
spring-context
<?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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.0.xsd
">
<bean id="userDao" class="com.qf.dao.dao.impl.UserDaoImp">
</bean>
<bean id="userService" class="com.qf.dao.service.impl.UserServiceImp">
<!-- 注入userDaoImp对象,需要set方法-->
<property name="userDaoImp" ref="userDao"></property>
</bean>
<!--作用域 prototype 每次从容器中获取的对象都是新的对象 singleton 单例对象 默认值 每次都是相同的对象-->
<bean id="test" class="com.qf.dao.pojo.TestScope" scope="prototype"></bean>
<!--<bean id="test" class="com.qf.dao.pojo.TestScope" scope="singleton"></bean>
结果:test1 = com.qf.dao.pojo.TestScope@5ec0a365
test2 = com.qf.dao.pojo.TestScope@5ec0a365
<bean id="test" class="com.qf.dao.pojo.TestScope" scope="prototype"></bean>
结果: test1 = com.qf.dao.pojo.TestScope@4fe3c938
test2 = com.qf.dao.pojo.TestScope@5383967b
-->
<bean id="life" class="com.qf.dao.pojo.TestLife" init-method="init" destroy-method="destroy"></bean>
<bean id="pojo" class="com.qf.dao.pojo.TestPojo">
<property name="list" >
<list>
<value>2</value>
<value>22</value>
<value>5</value>
</list>
</property>
<property name="map">
<map>
<entry key="name" value="张三"></entry>
</map>
</property>
<property name="name" value="小明"></property>
<property name="age" value="12"></property>
</bean>
<bean id="pojo1" class="com.qf.dao.pojo.TestPojo"
p:age="12"
p:name="李四"
p:list-ref="list"
p:map-ref="map"
/>
<util:list id="list">
<value>12</value>
<value>23</value>
</util:list>
<util:map id="map">
<entry key="name" value="王五"></entry>
</util:map>
</beans>
public class iocTestMain2 {
public static void main(String[] args) {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring-context.xml");
TestPojo pojo = classPathXmlApplicationContext.getBean("pojo", TestPojo.class);
System.out.println("pojo = " + pojo);
}
}
//结果:
//初始化
//pojo = TestPojo{list=[2, 22, 5], map={name=张三}, name='小明', age=12}
public class iocTestMain2 {
public static void main(String[] args) {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring-context.xml");
TestLife life = classPathXmlApplicationContext.getBean("life", TestLife.class);
classPathXmlApplicationContext.close();
}
}
//结果:
//初始化
//销毁
public class TestLife {
public TestLife() {
}
public void init(){
System.out.println("初始化");
}
public void destroy(){
System.out.println("销毁");
}
}
属性
init-method
<!--init-method="init" 初始化连接-->
<bean id="life" class="com.smart.pojo.TestLife"
init-method="init"
/>
destroy-method
<!--destroy-method="destroy" 容器关闭则执行-->
<bean id="life" class="com.smart.pojo.TestLife" destroy-method="destroy"/>
id
声明对象的在容器中的键
class
在容器需要创建对象的类
scope
对象的作用域
- singleton 单例对象 默认值
- prototype 每次从容器中获取对象都是新创建对象
- request 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于web的Spring WebApplicationContext环境
- sesison 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean。该作用域仅适用于web的Spring WebApplicationContext环境
- globalSession 为每个全局的HTTP会话创建一个实例
初始化赋值
- 通过constructor-arg
- 通过property子标签
简写方式
- p 表示 property的缩写
xmlns:p="http://www.springframework.org/schema/p"
- util 声明 list map array
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.0.xsd
">
2.注解
spring-ioc-注解
- 必须开启注解支持,在xml文件中使用 context:component-scan
<!--base-package指定了路径,然后就扫描遍历该路径下面所有的.class文件,通过反射可以拿到一个个对象,怎么知道要把哪个对象注入容器呢,关键就在哪个类上面有注解就把哪个类注入容器,如下面的UserServiceImp类上面有@service注解,就把UserServiceImp注入容器-->
xmlns:context="http://www.springframework.org/schema/context"
<context:component-scan base-package="根包名"/>
<!--例如-->
<context:component-scan base-package="com.qf.dao.controller"/>
使用原则
1.能使用注解尽量使用注解
2.第三方框架 xml方式
核心注解
注册到容器
- @Component 通用注解,当一个类需要注入的容器中,没有特定的语义可以使用该注解
- @Service 在业务层使用
- @Repository 使用在数据层
- @Controller 在控制层
public class iocTestMain2 {
public static void main(String[] args) {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring-context1.xml");
UserService userServiceImp = classPathXmlApplicationContext.getBean("userServiceImp", UserService.class);
System.out.println("userServiceImp = " + userServiceImp);
}
}
//如果括号里面内容为空,默认名字为类名的首字母小写,即userServiceImp,特殊情况为如果类的名字开头几个字母都为大写,则原封不动的命名,如ABUserServiceImp
@Service()
public class UserServiceImp implements UserService {
private UserDaoImp userDaoImp;
public void setUserDaoImp(UserDaoImp userDaoImp) {
this.userDaoImp = userDaoImp;
}
@Override
public Integer login() {
return userDaoImp.insert();
}
}
依赖注入
- @Autowired
- @Resource
- 构造方法注入
- setter方法注入
- 属性注入
3.1 Autowired spring 自带的注解
- Autowired 通过类型去查找依赖对象,如果类型不存在或者存在多个类型直接报错
3.2 Resource jsr250定义的
在不使用任何属性的情况下 先根据名字查找对象(属性的名字) 如果通过属性名找不到依赖对象直接通过类型查找,如果类型不存在或者存在多个类型直接报错
如果指定了name属性的值 只会通过name属性来查找对象,如果找不到直接抛出异常
如果指定了type属性的值 只会通过类型去查找对象,如果查找类型不存在或者存在多个 直接抛出异常
3.3 Inject jsr330定义的
@Service()
public class UserServiceImp implements UserService {
@Autowired
@Resource
private UserDaoImp userDaoImp;
public void setUserDaoImp(UserDaoImp userDaoImp) {
this.userDaoImp = userDaoImp;
}
@Override
public Integer login() {
return userDaoImp.insert();
}
}
其他注解
@Qualifier
配合Autowired使用,通过名字来查找依赖对象
@Primary
如果容器中存在多个实现类,可以使用@Primary注解来提高优先级
Java Config
/** * 在配置类上使用注解 @Configuration * 在类中定义方法 * 1 必须是public 声明 * 2 必须有返回值 * 3 必须在方法上面使用@Bean 注解 */ @Configuration public class DruidConfig { @Bean public DataSource dataSource() throws Exception { HashMap<String, String> map = new HashMap<>(); map.put("username", "root"); map.put("password", "root"); map.put("url", ""); return DruidDataSourceFactory.createDataSource(map); } }
三 spring-MVC
Spring的web模型 - 视图 - 控制器(MVC)
前后端分离
- 浏览器 —>发送请求—> 请求前端xxx.html界面 —>浏览器解析–>js 图片 ajax–>请求服务(ajax)–>请求服务接口
- 后台 --> 控制层 —>业务层 —>数据层 -->数据库
响应数据 --> json
- 前端—> 接收解析json -> DOM操作—>展示数据
前后端不分离
浏览器—> 请求后台路径 (http://localhost:8080/anno/body) —> 控制层 —>业务层---->数据层—>数据库
浏览器发送请求->DispatcherServlet(HandlerMapping里面有键值对,键为路径,值为Controller,然后找到Controller的核心方法)->业务层->数据层->数据库
渲染 xxx.jsp + 数据 – > xxx.html
Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦,基于请求驱动指的就是使用请求-响应模型。
前端控制器是DispatcherServlet;应用控制器其实拆为处理器映射器(Handler Mapping)进行处理器管理和视图解析器(View Resolver)进行视图管理;页面控制器/动作/处理器为Controller接口(仅包含ModelAndView handleRequest(request, response) 方法)的实现(也可以是任何的POJO类)(“普通Java对象”(Plain Old Java Object)的定义,简称POJO)
在Spring的Web MVC框架提供了模型 - 视图 - 控制器架构以及可用于开发灵活,松散耦合的Web应用程序准备的组件。 MVC模式会导致分离的应用程序(输入逻辑,业务逻辑和UI逻辑)的不同方面,同时提供这些元素之间的松耦合。
模型(Model )封装了应用程序的数据和一般他们会组成的POJO。
视图(View)是负责呈现模型数据和一般它生成的HTML输出,客户端的浏览器能够解释。
控制器(Controller )负责处理用户的请求,并建立适当的模型,并把它传递给视图渲染。
DispatcherServlet
Spring的web模型 - 视图 - 控制器(MVC)框架是围绕着处理所有的HTTP请求和响应的DispatcherServlet的设计。 Spring的Web MVC框架的DispatcherServlet的请求处理流程说明如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-btHo3ryM-1653495489176)(E:\培训\第二阶段\总结\pictrue\mvc.webp)]
下面是对应于传入的HTTP请求到DispatcherServlet的事件序列:
1.接收HTTP请求后,DispatcherServlet 咨询 HandlerMapping 来调用相应的控制器。DispatcherServlet(HandlerMapping里面有键值对,键为路径,值为Controller,然后找到Controller的核心方法)
2.该控制器接受请求并调用基于使用GET或POST方法相应的服务方法。服务方法将基于定义的业务逻辑设置模型数据,并返回视图名到DispatcherServlet。
3.DispatcherServlet将需要帮助的ViewResolver从拾取到该请求所定义的视图。
4.一旦视图被敲定,DispatcherServlet会传递模型数据是在浏览器上最终呈现的视图。
所有上述部件,即HandlerMapping,控制器和视图解析器WebApplicationContext 部分是纯的 ApplicationContext 必要的 Web应用程序的一些额外的功能扩展。
作用
Spring 进行web开发
对Servlet进行封装简化
如何使用
1 导入依赖
<!--导入spring-mvc框架-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.9</version>
</dependency>
2 配置文件
spring-context.xml
- context:component-scan
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启spring对注解的支持 -->
<context:component-scan base-package="com.smart.mvc"/>
</beans>
spring-mvc.xml
- context:component-scan
- mvc:annotation-driven
<?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: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/context https://www.springframework.org/schema/context/spring-context.xs http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- Spring MVC的注解扫描功能,允许做出像 @Controller和使用@RequestMapping注解等使用-->
<context:component-scan base-package="com.smart.mvc.controller"/>
<!-- 开启webmvc注解支持-->
<mvc:annotation-driven/>
</beans>
web.xml
- Spring其他的配置文件初始化
- 注册DispatchServlet
- 初始化配置文件
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<display-name>Archetype Created Web Application</display-name>
<!--listener用来 把配置文件交给spring加载-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--servlet启动之后加载该配置文件,classpath:不加*只会加载classpath根目录下面的配置文件,classpath*:加*则会加载根目录下面的所有配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring-context.xml</param-value>
</context-param>
<!--给servlet命名-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--servlet启动之前加载该配置文件,classpath:不加*只会加载classpath根目录下面的配置文件,classpath*:加*则会加载根目录下面的所有配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
</servlet>
<!--给servlet作映射,拦截浏览器请求的所有路径-->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
控制层
@Controller
public class UserController {
@RequestMapping("/test")
@ResponseBody
public String test() {
return "test";
}
}
@RequestMapping
作用
路径映射
可以使用在类上面,也可以在方法上使用
用在方法上可以限制请求方式
源代码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
String name() default "";
@AliasFor("path")
String[] value() default {};//映射的路径
@AliasFor("value")
String[] path() default {};//映射的路径
RequestMethod[] method() default {};//浏览器的请求方式
String[] params() default {};//表示请求的参数中必须包含指定的键值,如param={"id=1"},表示请求中必须有id=1才会接收
String[] headers() default {};//表示请求头必须包含指定的键值,如header={"token=123"},表示请求中必须有token=123才会接收
String[] consumes() default {};//处理指定http的提交内容,编码格式(Content-Type)
String[] produces() default {};//请求头中Accept类型中必须包含指定的值,如produces={"application/json"},请求头中Accept类型中必须包含application/json才会接收
}
请求方式:
- get
- post
- put
- delete
- head
了解:
- patch
- trace
- options
属性详解
属性 | 说明 | |
---|---|---|
value/path(重点) | 表示映射的路径 | |
method(重点) | 限制客户端的请求方式 | |
consumes(了解) | 处置指定的提交内容(Content-Type) text/html image/* application/json | |
params(了解) | 表示请求的参数中必须包含指定的键值 | |
headers(掌握) | 表示请求头必须包含指定的键值 | |
produces(了解) | 请求头中Accept类型中必须必须包含指定的值 |
补充
路径映射支持通配符 ANT风格
- ? 配置路径中的单个字母
*
匹配一级路径- ** 匹配多级路径
例子
/user/* 匹配一级路径 例如:user/list /user/info 成功 一级路径以上就会失败:user/info/detail 失败
/user/** 匹配多级路径:ser/list /user/info user/info/detail 成功
扩展注解
请求方式的简化
- @GetMapping
- @PostMapping
- @PutMapping
- @DeleteMapping
二 请求参数处理
- 普通参数
- 参数注解
普通参数
QO:query object 查询对象
PO:entity 持久化对象
- 基本类型的参数(掌握)
- 对象(掌握)
- 复杂对象
- 集合类型(不支持集合直接作为请求参数,需要配合注解使用)
- map
- 数组
- json数据(掌握)
- 内置的对象类型
参数注解
必须掌握
- @ReqeustParam
- @ReqeustBody
- @PathVariable
其他注解
- @ReqeustHeader
- @CookieValue
- @SessionAttribute
- @ModelAttribute
@ReqeustParam
//@RequestParam:将请求参数绑定到你控制器的方法参数上(是springmvc中接收普通参数的注解)
@Controller
@RequestMapping("hello")
public class HelloController2 {
/**
* 接收普通请求参数
* http://localhost:8080/hello/show16?name=linuxsir
* url参数中的name必须要和@RequestParam("name")一致
* @return
*/
@RequestMapping("show16")
public ModelAndView test16(@RequestParam("name")String name){
ModelAndView mv = new ModelAndView();
mv.setViewName("hello2");
mv.addObject("msg", "接收普通的请求参数:" + name);
return mv;
}
/**
* 接收普通请求参数
* http://localhost:8080/hello/show17
* url中没有name参数不会报错、有就显示出来
* @return
*/
@RequestMapping("show17")
public ModelAndView test17(@RequestParam(value="name",required=false)String name){
ModelAndView mv = new ModelAndView();
mv.setViewName("hello2");
mv.addObject("msg", "接收普通请求参数:" + name);
return mv;
}
/**
* 接收普通请求参数
* http://localhost:8080/hello/show18?name=998 显示为998
* http://localhost:8080/hello/show18?name 显示为hello
* @return
*/
@RequestMapping("show18")
public ModelAndView test18(@RequestParam(value="name",required=true,defaultValue="hello")String name){
ModelAndView mv = new ModelAndView();
mv.setViewName("hello2");
mv.addObject("msg", "接收普通请求参数:" + name);
return mv;
}
批量删除
@GetMapping("/del")
public String del(@RequestParam List<Integer> ids) {
ids.forEach(System.out::println);
return "list作为参数";
}
作用
- 给参数起别名
- 默认情况下是必要参数
- 给参数设置默认值,当设置了默认值,必要参数就会设置成false
源码
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
@AliasFor("name")
String value() default "";//给参数起别名
@AliasFor("value")
String name() default "";
boolean required() default true;//是否是必要参数,默认是必要参数
String defaultValue() default ValueConstants.DEFAULT_NONE;//给参数设置默认值
}
属性说明
属性 | 说明 | |
---|---|---|
defaultValue | 给参数设置默认值 | |
value | 给参数起别名 | |
required | 是否是必要参数,默认是必要参数 |
@ReqeustBody
作用
获取请求体中的数据,只能用POST请求和PUT请求
应用场景 通常情况下接收客户端的json数据自动转化对象
@ResponseBody 通常情况下服务端发送的对象自动转化成json数据
源码
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBody {
boolean required() default true;
}
例子
@Data
public class UserReqeustParams {
@JSONField(name = "user_name")
private String username;
private int page;
private int size;
@JSONField
private String password;
private String phone;
@JSONField(deserialize = false)
private List<String> addressList;
@JSONField(format = "yyyy-MM-dd")
private Date createData;
@JSONField(serialize = false)
private boolean status;
}
@PostMapping("/body")
@ResponseBody
public String body(@RequestBody UserReqeustParams user) {
System.out.println(user.toString());
return "接受客服端的json数据";
}
参数
{
"user_name": "admin",
"page": 1,
"size": 10,
"password": "123456",
"phone": "123456",
"addressList": [1,2,3,4],
"createData": "2021-09-24 15:39:25",
"status": true
}
集成fastjson
导入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
配置
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<!--解决IE浏览器将json数据当做文件来下载-->
<value>application/json;charset=UTF-8</value>
</list>
</property>
<property name="fastJsonConfig">
<bean class="com.alibaba.fastjson.support.config.FastJsonConfig">
<!--指定日期的全局通用格式-->
<property name="dateFormat" value="yyyy-MM-dd HH:mm:ss" />
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
public @interface JSONField {
// 设置别名
String name() default "";
//设置日期格式
String format() default "";
// 是否序列化,序列化就是对象转json格式
boolean serialize() default true;
// 是否反序列化,反序列化就是json转对象
boolean deserialize() default true;
}
@Data
public class UserReqeustParams {
//fastjson传过来的名字为user_name,就需要设置别名
@JSONField(name = "user_name")
private String username;
private int page;
private int size;
@JSONField
private String password;
private String phone;
@JSONField(deserialize = false)
private List<String> addressList;
//设置日期格式
@JSONField(format = "yyyy-MM-dd")
private Date createData;
@JSONField(serialize = false)
private boolean status;
}
@PathVariable
作用
从请求路径中获取参数信息
注意:
RESTFul风格
- 只能声明一些简单的 参数 例如 id page size 等 经常使用在get delete 请求
源代码
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PathVariable {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
boolean required() default true;
}
属性说明
属性 | 说明 | |
---|---|---|
value/name | 给参数起别名 | |
required | 是否是必要参数, 默认必要参数 | |
例子
// product/1
@GetMapping("/product/{pid}")
@ResponseBody
public String body(@PathVariable("pid") Long id) {
return "接受客服端的json数据";
}
// list/2/20
@GetMapping("/list/{page}/{size}")
@ResponseBody
public String list(@PathVariable int page, @PathVariable int size) {
return "接受客服端的json数据";
}
三 响应内容
概要
- 前后分离
- 前后端不分离
-
前后端分离
-
- 浏览器—发送请求— 请求前端xxx.html界面 —>浏览器解析–js 图片 ajax-- 请求服务(ajax)–请求服务接口
-
- 后台 --> 控制层 —>业务层 —>数据层 -->数据库
-
响应数据 --> json
-
- 前端—> 接收解析json -> DOM操作—>展示数据
-
-
前后端不分离
-
-
浏览器-> 请求后台路径 (http://localhost:8080/anno/body) —> 控制层 —>业务层---->数据层—>数据库
浏览器发送请求->DispatcherServlet(HandlerMapping里面有键值对,键为路径,值为Controller,然后找到Controller的核心方法)->业务层->数据层->数据库
-
-
渲染 xxx.jsp + 数据 – > xxx.html
分离
- 核心注解 @RestController = @Controller + @ResponseBody
- 在类上面使用 @RestController
- 方法的返回值可以是任意对象 不需要进行json转化
注意:
- 依赖于第三方json框架 fastjson jackson gson
不分离
- 可以返回字符串 也可以是ModelAndView对象
- 转化跟重定向
- 转发 forward:站内路径
- 重定向 redirect:“站内路径|站外路径”
注意:
-
配置视图解析器
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/views/" p:suffix=".jsp" /> <!-- 配置静态资源不拦截--> <mvc:default-servlet-handler />
Mybatis
一 概要
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
二 传统JDBC开发的缺点
-
频繁的建立连接释放资源 可以复用连接对象,提供性能 (连接池)
-
SQL语句跟Java代码耦合在一起
- 需求一旦发生改变,维护起来比较困难
- 可读性比较差
解决方案
- 将SQL语句跟Java代码分离
-
JDBC参数问题 存在一定的局限性
- 传递参数的顺序
- 条件判断
解决方案
- 对参数进行封装,不需要关注参数的传递顺序
- 动态SQL
-
结果集处理
- 从rs对象获取值,在封装成对象
解决方案
- 对结果集的处理进行统一的封装 (将数据的结果集转化成对象)
备注
ORM 是 (Object Relation Mapping)
用户面向对象的方式操作数据库
- 类名 —> 表名
- 属性 —> 列名
- 类型 ---- 类型
- 对象 ----> 记录
当列名跟属性,表名跟类命名不一致的情况下该怎么办?
- 注解
- ResultMap
三 基础使用
1 导入依赖
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
2 核心配置文件
<?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>
<!-- 开发环境 生产环境 测试环境 -->
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="url" value="jdbc:mysql://114.215.196.38:3306/db_mybatis"/>
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
</configuration>
3 定义mapper接口
/**
* 1> 注意接口中的所有方法的命名全部采用SQL的关键句开头
*/
public interface UserMapper {
/**
* 查询所有的用户信息
*
* @return
*/
List<User> selectList();
/**
* 保存用户
*/
int insert(User user);
}
4 定义mapper.xml文件
-
insert标签
-
参数处理
- 在xml文件中parameterType属性
- 在接口中使用注解 @Param
-
在mapper,xml文件中如何使用参数
#{变量}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
"http://ibatis.apache.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.smart.mybatis.mapper.UserMapper">
<insert id="insert" parameterType="com.smart.mybatis.entity.User">
INSERT INTO t_user(username, password, phone)
VALUES (#{user.username}, #{user.password}, #{user.phone})
</insert>
</mapper>
5 在核心配置文件中注册
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
6 测试代码
public class TestMain {
/**
* @param args
*/
public static void main(String[] args) {
InputStream resourceAsStream = TestMain.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession session = ssf.openSession(true);
UserMapper mapper = session.getMapper(UserMapper.class);
User user = new User();
user.setUsername("admin");
user.setPassword("123456");
user.setPhone("123");
mapper.insert(user);
}
}
SSM整合
SSM(Spring+SpringMVC+MyBatis)框架集由Spring、MyBatis两个开源框架整合而成(SpringMVC是Spring中的部分内容)
Spring IoC 配置
Spring mvc 配置
Spring-mybatis 配置
一 技术栈
- spring
- springmvc
- mybatis
- druid
- jackson/fastjson
二 SS整合
1 导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
2 配置文件
spring-context.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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.smart.ssm"/>
</beans>
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: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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.smart.ssm.controller"/>
<mvc:annotation-driven/>
</beans>
三 SM整合
1 导入相关依赖
<!-- 1 驱动 2 数据库连接池 Druid 3 Mybatis -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.7</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
2 配置文件
所有的配置文件最终都会放到同一个容器里面,就像很多独立的章节,最终都会变成一篇论文,可以相互连接的
- 注册SQLSessionFactory对象
- 扫描所有的mapper.xml文件
- 配置数据库连接池
- 注册扫描所有的接口
- MapperScannerConfigurer
- 连接池的配置
UserMapper.xml
所有数据层的接口实现类用xxMapper.xml文件来写
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
"http://ibatis.apache.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.ssm.mapper.UserMapper">
<insert id="insert" parameterType="User">
insert into t_user(username,password) values (#{user.username},#{user.password})
</insert>
</mapper>
mybatis-config.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>
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
</configuration>
spring-mybatis.xml
注册对象有三种方式,xml、注解、java config,由于mybatis是第三方框架,使用注解难以注册,所以使用xml方式,mybatis就是对传统的JDBC进行封装2,所以就需要数据库连接池、加载mapper.xml文件里面的sql语句,结果集等等
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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.xsd">
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 1 (必要属性配置) 注册所有的mapper.xml文件-->
<property name="mapperLocations" value="classpath*:/mapper/**/*.xml"/>
<!-- 2 (必要配置) 数据库连接池 -->
<property name="dataSource" ref="dataSource"/>
<!-- 3 配置 类别名
如果在mapper.xml中有parameterType这个属性,则配置了下面这个属性就不需要在parameterType这个属性内容加前缀com.smart.ssm.entity
<insert id="insert" parameterType="User">
</insert>-->
<property name="typeAliasesPackage" value="com.smart.ssm.entity"/>
<!-- 4 配置加载核心配置文件-->
<!-- <property name="configLocation" value="classpath:mybatis-config.xml"/> 该方式需要独立创建一个mybatis-config.xml文件 文件内容为
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>,和下面的一起两种方式,开发中这一种方式比较多-->
<!-- 用来修改mybatis框架里面默认的一些配置-->
<property name="configuration">
<bean class="org.apache.ibatis.session.Configuration">
<property name="lazyLoadingEnabled" value="true"/>
</bean>
</property>
</bean>
<!--没用mybatis框架之前的做法:Dao接口 Dao实现类 @Repository -->
<!-- 使用框架之后:把所有的接口注册到容器里-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--把上面的bean注册到容器中,id为sqlSessionFactory-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!--注册 mapper接口-->
<property name="basePackage" value="com.smart.ssm.mapper"/>
</bean>
</beans>
spring-durid.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="maxActive" value="20"/>
<property name="url" value="${jdbc_url}"/>
<property name="username" value="${jdbc_user}"/>
<property name="password" value="${jdbc_password}"/>
<property name="filters" value="stat"/>
<property name="initialSize" value="1"/>
<property name="maxWait" value="6000"/>
<property name="minIdle" value="1"/>
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<property name="minEvictableIdleTimeMillis" value="300000"/>
<property name="testWhileIdle" value="true"/>
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
<property name="poolPreparedStatements" value="true"/>
<property name="maxOpenPreparedStatements" value="20"/>
<property name="asyncInit" value="true"/>
</bean>
</beans>
常见错误
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.mybatis.spring.mapper.MapperScannerConfigurer#0' defined in file [D:\work\IdeaProjects\2105\ssm-example\target\ssm-exmaple\WEB-INF\classes\spring-mybatis.xml]: Initialization of bean failed; nested exception is java.lang.NoClassDefFoundError: org/springframework/dao/support/DaoSupport
解决:导入spring-orm依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.9</version>
</dependency>
四 web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<display-name>Archetype Created Web Application</display-name>
<!--listener用来 把配置文件交给spring加载-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--servlet启动之后加载该配置文件,classpath:不加*只会加载classpath根目录下面的配置文件,classpath*:加*则会加载根目录下面的所有配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-*.xml</param-value>
</context-param>
<!--给servlet命名-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--servlet启动之前加载该配置文件,classpath:不加*只会加载classpath根目录下面的配置文件,classpath*:加*则会加载根目录下面的所有配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
</servlet>
<!--给servlet作映射,拦截浏览器请求的所有路径-->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
XML映射器
一 概要
-
mapper.xml文件(标签)
-
增删改
-
查
多表查询(join)
-
结果集标签
-
-
动态SQL
MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。
核心顶级标签
-
insert
– 映射插入语句。 -
update
– 映射更新语句。 -
delete
– 映射删除语句。 -
select
– 映射查询语句。 -
resultMap
– 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
其他
cache
– 该命名空间的缓存配置。cache-ref
– 引用其它命名空间的缓存配置。sql
– 可被其它语句引用的可重用语句块。
二 查询(单表)
通过select标签来实现
常用属性
属性 | 说明 | |
---|---|---|
id | 对应接口的名称 | |
parameterType | 推荐使用@Param来传递参数 (开发中能不用就不用) | |
resultType | 返回查询时候指定的类型(开发中建议不要使用) | |
resultMap | 配合resultMap标签使用 跟resultType属性只能选其一 |
例子
1 mapper接口
public interface UserMapper {
/**
* 返回单个对象
*/
User selectById(@Param("uid") Long uid);
/**
* 分页查询
*/
List<User> selectList(@Param("limit") int limit, @Param("offset") int offset);
}
2 mapper映射
<!--其中 selectById为接口中对应的方法,resultMap="BaseResultMap"为查询后映射到结果集的id,要告诉它映射到哪一个结果集-->
<select id="selectById" resultMap="BaseResultMap">
SELECT u.uid, u.username, u.password, u.phone, u.create_date, u.status
FROM t_user u
WHERE u.uid = #{uid}
</select>
<!-- -->
<select id="selectList" resultMap="BaseResultMap">
SELECT u.uid, u.username, u.password, u.phone, u.create_date, u.status
FROM t_user u
WHERE status = 1
LIMIT #{limit} , #{offset}
</select>
3 resultMap
<!--id="BaseResultMap"为查询后mapper映射的结果集id,告诉它是映射到这个结果集,type="User"为映射结果集的类,即结果集中结果集的对象类型-->
<resultMap id="BaseResultMap" type="User" extends="ResultMap1">
<!-- 主键的映射-->
<result property="status" column="status"/>
<result property="createDate" column="create_date"/>
</resultMap>
<resultMap id="ResultMap1" type="User">
<!-- 主键的映射-->
<id property="uid" column="uid"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="phone" column="phone"/>
</resultMap>
4 单元测试
@SpringJUnitConfig(
locations =
{"classpath:spring-context.xml",
"classpath:spring-mvc.xml",
"classpath:spring-mybatis.xml",
"classpath:spring-druid.xml"})
public class UserMapperTest {
@Resource
UserMapper userMapper;
@Test
void selectById() {
User user = userMapper.selectById(1L);
System.out.println(user.toString());
}
@Test
void selectList() {
List<User> users = userMapper.selectList(0, 10);
users.forEach(user -> System.out.println(user.getUsername()));
}
}
三 resultMap
概要
定义结果集映射
- 属性跟列名映射
属性
属性 | 说明 | |
---|---|---|
id | 唯一标识 | |
type | Entity类型 映射结果集的类型 | |
extends | 继承其他的resultMap |
子标签
标签名 | 说明 | |
---|---|---|
id | 定义主键 | |
result | 定义普通的字段与属性的对应关系 |
四 增删改
@Builder
@Builder,有@Builder要添加@NoArgsConstructor和@AllArgsConstructor,否则有可能映射不到构造方法
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
/**
*
*/
private Long uid;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 手机号
*/
private String phone;
}
public class UserMapperTest {
@Resource
UserMapper userMapper;
@Test
void insert() {
User user = User.builder()
.username("test11111")
.password("123456")
.phone("123132132")
.build();
userMapper.insert(user);
System.out.println(user.getUid());
}
}
BeanUtils.copyProperties
使用BeanUtils.copyProperties就不需要其他注解了
@Data
public class Access {
/**
* 门禁id
*/
private Long accessId;
/**
* 门禁编码
*/
private String accessNum;
/**
* 门禁名称
*/
private String accessName;
/**
* 门禁类型
*/
private String accessType;
/**
* 门禁位置
*/
private String accessSite;
/**
* 门禁方向
*/
private String direction;
/**
* 创建时间
*/
private Date createDate;
/**
* 描述
*/
private String description;
/**
* 是否删除 1正常 0删除
*/
private Boolean status;
}
使用BeanUtils.copyProperties(param,access)赋值
@Service
public class AccessServiceImpl implements AccessService {
@Resource
private AccessMapper accessMapper;
/**
*
* @param param
*@return java.lang.Boolean
*/
@Override
public Boolean insert(AccessRequestParam param) {
try {
Access access = new Access();
BeanUtils.copyProperties(param,access);
Integer insert = accessMapper.insert(access);
return insert>0;
} catch (BeansException e) {
throw new ServiceException(ResponseCode.SYS_ERROR);
}
}
@Override
public Boolean update(AccessRequestParam param) {
try {
Access access = new Access();
BeanUtils.copyProperties(param,access);
Integer update = accessMapper.update(access);
return update>0;
} catch (BeansException e) {
throw new ServiceException(ResponseCode.SYS_ERROR);
}
}
}
insert
作用
用户往数据中插入数据
属性
属性 | 说明 | |
---|---|---|
id | 对应接口方法的名字,如id=“insert” | |
useGeneratedKeys | 是否获取数据库自增主键,取值为true或false,如useGeneratedKeys=“true” | |
keyProperty | 赋值主键值的字段,如keyProperty=“user.uid” |
例子
mapper接口
public interface UserMapper {
int insert(@Param("user") User user);
}
mapper.xml
<insert id="insert">
INSERT INTO t_user (username, password, phone)
VALUES (#{user.username}, #{user.password}, #{user.phone})
</insert>
备注:
- 获取保存数据的主键的值的时候
例子
mapper接口
int insert(@Param("user") User user);
mapper.xml
<insert id="insert" keyProperty="user.uid" useGeneratedKeys="true">
INSERT INTO t_user (username, password, phone)
VALUES (#{user.username}, #{user.password}, #{user.phone})
</insert>
单元测试
@Test
void insert() {
User user = User.builder()
.username("test11111")
.password("123456")
.phone("123132132")
.build();
userMapper.insert(user);
System.out.println(user.getUid());
}
update/delete
作用
更新和删除数据
属性
属性 | 说明 | |
---|---|---|
id | 对应接口方法的名字 |
mapper接口
/**
*
* @param uid
* @return
*/
int delete(@Param("id") Long uid);
/**
* 数据库核心
*/
int update(@Param("user") User user);
mapper.xml
<update id="update">
UPDATE t_user u
SET u.username=#{user.username},
u.password=#{user.password},
u.phone= #{user.phone},
u.status = #{user.status}
WHERE u.uid = #{user.uid}
</update>
<delete id="delete">
UPDATE t_user u
SET u.status = 0
WHERE u.uid = #{id}
</delete>
测试代码
@Test
void delete() {
int delete = userMapper.delete(13L);
if (delete > 0) {
System.out.println("删除成功");
} else {
System.out.println("删除失败");
}
}
五 单元测试junit5
导入依赖
<!-- 单元测试核心依赖 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.9</version>
<scope>test</scope>
</dependency>
<!--单元测试时测试控制层需要这个依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
测试数据层
public interface UserMapper {
/**
* 返回单个对象
*/
User selectById(@Param("uid") Long uid);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
"http://ibatis.apache.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.smart.mapper.UserMapper">
<resultMap id="BaseResultMap" type="User" extends="ResultMap1">
<!-- 主键的映射-->
<result property="status" column="status"/>
<result property="createDate" column="create_date"/>
</resultMap>
<resultMap id="ResultMap1" type="User">
<!-- 主键的映射-->
<id property="uid" column="uid"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="phone" column="phone"/>
</resultMap>
<select id="selectById" resultMap="BaseResultMap">
SELECT u.uid, u.username, u.password, u.phone, u.create_date, u.status
FROM t_user u
WHERE u.uid = #{uid}
</select>
</mapper>
单元测试
ctrl + shift + t 创建单元测试类
@SpringJUnitConfig(
locations =
{"classpath:spring-context.xml",
"classpath:spring-mvc.xml",
"classpath:spring-mybatis.xml",
"classpath:spring-druid.xml"})
public class UserMapperTest {
@Resource
UserMapper userMapper;
@Test
void selectById() {
User user = userMapper.selectById(1L);
System.out.println(user.toString());
}
}
错误
Caused by: org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 9 in XML document from class path resource [spring-druid.xml] is invalid; nested exception is org.xml.sax.SAXParseException; lineNumber: 9; columnNumber: 49; cvc-complex-type.2.4.c: 通配符的匹配很全面, 但无法找到元素 'context:property-placeholder' 的声明。
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:402)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:338)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:310)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:188)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:224)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:195)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:257)
at org.springframework.test.context.support.AbstractGenericContextLoader.loadBeanDefinitions(AbstractGenericContextLoader.java:265)
at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:122)
at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:60)
at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.delegateLoading(AbstractDelegatingSmartContextLoader.java:275)
at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:243)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
... 69 more
Caused by: org.xml.sax.SAXParseException; lineNumber: 9; columnNumber: 49; cvc-complex-type.2.4.c: 通配符的匹配很全面, 但无法找到元素 'context:property-placeholder' 的声明。
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:203)
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:134)
at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:396)
at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:327)
at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:284)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator$XSIErrorReporter.reportError(XMLSchemaValidator.java:453)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.reportSchemaError(XMLSchemaValidator.java:3231)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.handleStartElement(XMLSchemaValidator.java:1912)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.emptyElement(XMLSchemaValidator.java:761)
at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.scanStartElement(XMLNSDocumentScannerImpl.java:351)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2784)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:602)
at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:112)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:505)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:842)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:771)
at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141)
at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:243)
at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:339)
at org.springframework.beans.factory.xml.DefaultDocumentLoader.loadDocument(DefaultDocumentLoader.java:77)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadDocument(XmlBeanDefinitionReader.java:432)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:390)
... 82 more
关联查询
一 概要
前面我们讲的都是单表查询,但企业开发中我们可能会碰到一些复杂的业务,这个时候可能需要多表查询
映射关系:
- 一对一
- 一对多(如果没有特殊说明,建议一对一采用一对多的方式设计) 多对一
- 多对多 角色跟权限(两个一对多)
二 关系模型(外键约束)
-
一对一
- 主表
- 从表
垂直分表
- 常用字段放主表
- 不常用字段放从表
外键字段
- 把主表的主键作为从表的外键 外键字段使用唯一约束
用户表 用户详情表 uid
-
一对多
外键不使用唯一约束
selelct * from t where 外键字段=值
-
多对多
- 两个一对多
- 必须有第三张表
- 性能问题
三 一对一
association标签
SQL 关系模型
主表
CREATE TABLE t_user
(
uid bigint AUTO_INCREMENT PRIMARY KEY,
username varchar(128) NOT NULL COMMENT '用户名',
password varchar(128) NOT NULL COMMENT '密码',
phone varchar(11) NOT NULL COMMENT '手机号',
create_date datetime DEFAULT CURRENT_TIMESTAMP NULL COMMENT '创建时间',
status int(1) DEFAULT 1 NOT NULL COMMENT '1 表示正常 0 表示删除',
CONSTRAINT phone UNIQUE (phone),
CONSTRAINT username UNIQUE (username)
);
从表
- 外键字段 uid
CREATE TABLE t_user_detail
(
user_detail_id bigint AUTO_INCREMENT PRIMARY KEY,
email varchar(255) UNIQUE NOT NULL COMMENT '用户邮箱',
status int(1) DEFAULT 1 NOT NULL COMMENT '是否删除 1 表示正常 0 表示删除',
/*一对一使用UNIQUE外键唯一约束*/
uid bigint UNIQUE NOT NULL COMMENT '用户ID',
/*外键约束 constraint 约束 foreign key 外键 references 映射*/
CONSTRAINT fk_user_uid FOREIGN KEY (uid) REFERENCES t_user (uid)
)
对象模型
@Data
– 注在类上,提供类的get、set、equals、hashCode、canEqual、toString方法
@NoArgsConstructor
– 这个注解可以生成无参构造方法
@AllArgsConstructor
– 这个注解可以生成全参构造函数,且默认不生成无参构造函数
@RequiredArgsConstructor
– 在类上使用,这个注解可以生成带参或者不带参的构造方法
用户对象
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
/**
* 关系模型 自己在User中加
*/
private UserDetail userDetail;
}
用户详情
@Data
public class UserDetail {
/**
* 用户ID
*/
private Long uid;
}
mapper接口
User selectByUsername(@Param("username") String username);
mapper.xml
resultMap
<!-- 通过用户名查询用户的所有信息 -->
<resultMap id="UserDetailResultMap" type="User">
<id property="uid" column="uid"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="phone" column="phone"/>
<result property="status" column="status"/>
<result property="createDate" column="create_date"/>
<!-- 表示一对一的关系 这里的userDetail是User里面的userDetail属性 -->
<association property="userDetail" resultMap="DetailBaseResultMap"/>
</resultMap>
<!-- 通过用户名查询用户的所有信息 -->
<resultMap id="DetailBaseResultMap" type="UserDetail">
<id property="userDetailId" column="user_detail_id"/>
<result property="email" column="email"/>
<result property="status" column="status"/>
<result property="uid" column="uid"/>
</resultMap>
<!-- 普通多表 < 子查询 < 外连接 左外连接 右外连接 全连接 -->
<select id="selectByUsername" resultMap="UserDetailResultMap">
SELECT u.uid,
u.username,
u.password,
u.phone,
u.create_date,
u.status,
ud.user_detail_id,
ud.email,
ud.status,
ud.uid
FROM t_user u
LEFT JOIN t_user_detail ud ON u.uid = ud.uid
WHERE u.username = #{username}
</select>
测试代码
@Test
void selectUserByName() {
User user = userMapper.selectByUsername("霞霞1号");
System.out.println(user.getUserDetail().getEmail());
}
四 一对多
1 概要
一方持有多方的引用
如:
用户有多个订单 ,一个订单有多个商品
一级菜单包含多个二级菜单
2 SQL(关系模型)
用户表
CREATE TABLE t_user
(
uid bigint AUTO_INCREMENT PRIMARY KEY,
username varchar(128) NOT NULL COMMENT '用户名',
password varchar(128) NOT NULL COMMENT '密码',
phone varchar(11) NOT NULL COMMENT '手机号',
create_date datetime DEFAULT CURRENT_TIMESTAMP NULL COMMENT '创建时间',
status int(1) DEFAULT 1 NOT NULL COMMENT '1 表示正常 0 表示删除',
CONSTRAINT phone UNIQUE (phone),
CONSTRAINT username UNIQUE (username)
);
订单表
CREATE TABLE t_order
(
order_id bigint AUTO_INCREMENT
PRIMARY KEY,
order_no bigint NOT NULL COMMENT '订单号',
msg varchar(255) NULL COMMENT '附加信息',
status int(1) DEFAULT 1 NULL,
uid bigint NOT NULL COMMENT '用户ID',
CONSTRAINT order_no
UNIQUE (order_no)
);
3 对象模型
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
/**
* 用户的订单信息 自己在User里加
*/
private List<Order> orders;
}
@Data
public class Order {
/**
*
*/
private Long orderId;
/**
* 订单号
*/
private Long orderNo;
/**
* 附加信息
*/
private String msg;
/**
*
*/
private Integer status;
/**
* 用户ID
*/
private Long uid;
}
4 mapper接口
User selectOrderById(@Param("uid") Long uid);
5 mapper.xml
<!-- -->
<select id="selectOrderById" resultMap="UserOrderResultMap">
SELECT u.uid,
u.username,
u.password,
u.phone,
u.create_date,
u.status,
o.order_id,
o.order_no,
o.msg,
o.status os,
o.uid
FROM t_user u
LEFT JOIN t_order o ON u.uid = o.uid
WHERE u.uid = #{uid}
</select>
6 ResultMap
<!-- 通过用户id查询用户的所有订单 -->
<resultMap id="UserOrderResultMap" type="User">
<id property="uid" column="uid"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="phone" column="phone"/>
<result property="status" column="status"/>
<result property="createDate" column="create_date"/>
<!--这里的orders是User里面的orders属性-->
<collection property="orders" resultMap="OrderResultMap"/>
</resultMap>
<resultMap id="OrderResultMap" type="Order">
<id property="orderId" column="order_id"/>
<result property="uid" column="uid"/>
<result property="orderNo" column="order_no"/>
<result property="msg" column="msg"/>
<result property="status" column="os"/>
</resultMap>
7 补充
如果多的一方的数据量比较大会产生性能问题,
可以在collection标签中使用selelct属性来实现分页查询
用户相关
UserMapper 接口
//两个接口都通过uid来查询
User selectOrderById(@Param("uid") Long uid);
UserMapper.xml
<select id="selectOrderById" resultMap="UserDetailResultMap">
SELECT *
FROM t_user
WHERE uid = #{uid}
</select>
ResultMap
- column 指定外键字段
- select = 查询多方的接口
<resultMap id="UserDetailResultMap" type="User">
<id property="uid" column="uid"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="phone" column="phone"/>
<result property="status" column="status"/>
<result property="createDate" column="create_date"/>
<!-- 一对多 多的一个放数据量比较大的话建立使用这种方式 com.smart.mapper.OrderMapper.selectOrders是指com.smart路径下面的mapper文件里OrderMapper接口的selectOrder方法-->
<collection property="orders"
column="uid"
select="com.smart.mapper.OrderMapper.selectOrders"/>
</resultMap>
订单相关
OrderMapper 接口
//两个接口都通过uid来查询,只是OrderMapper接口通过selectOrders方法,通过uid查询到的用户的所有订单封装到List<Order>传给User对象
public interface OrderMapper {
List<Order> selectOrders(@Param("uid") Long uid);
}
OrderMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
"http://ibatis.apache.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.smart.mapper.OrderMapper">
<select id="selectOrders" resultMap="BaseResultMap">
SELECT *
FROM t_order
WHERE uid = #{uid}
LIMIT 0,10
</select>
</mapper>
ResultMap
<resultMap id="BaseResultMap" type="Order">
<id property="orderId" column="order_id"/>
<result property="uid" column="uid"/>
<result property="orderNo" column="order_no"/>
<result property="msg" column="msg"/>
<result property="status" column="status" />
</resultMap>
五 多对多
两个一对多
角色 权限
- 部门经理 炒鸡管理员
- 增 删 改 查
关系模型(SQL)
- 角色表
- 权限表
- 中间表
角色表
CREATE TABLE t_role
(
role_id int AUTO_INCREMENT,
name varchar(128) NOT NULL COMMENT '角色的名字',
role_dec varchar(255) COMMENT '角色说明',
create_date DATETIME DEFAULT NOW() COMMENT '创建时间',
status int DEFAULT 1 NOT NULL COMMENT '是否删除 1 表示正常 0 表示删除',
CONSTRAINT pk_role_id PRIMARY KEY (role_id),
CONSTRAINT uk_role_name UNIQUE (name)
);
CREATE TABLE t_permission
(
per_id int AUTO_INCREMENT PRIMARY KEY,
name varchar(128) NOT NULL COMMENT '权限名称',
per_dec varchar(255) COMMENT '权限说明',
create_date DATETIME DEFAULT NOW() COMMENT '创建时间',
status int DEFAULT 1 NOT NULL COMMENT '是否删除 1 表示正常 0 表示删除',
CONSTRAINT uk_permission_name UNIQUE (name)
);
# 角色权限中间表
CREATE TABLE role_permission_relation
(
id int AUTO_INCREMENT PRIMARY KEY,
per_id int NOT NULL COMMENT '权限ID',
role_id int NOT NULL COMMENT '角色ID'
)
对象模型
@Data
public class Role {
//...
private List<Permission> permissions;
}
@Data
public class Permission {
private List<Role> roles;
}
mapper接口
public interface RoleMapper {
/**
* 查询所有的角色权限信息
*/
List<Role> selectList();
}
public interface PermissionMapper {
}
mapper 映射
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
"http://ibatis.apache.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.smart.mapper.RoleMapper">
<resultMap id="RolePermissionResultMap" type="Role">
<id property="roleId" column="role_id"/>
<result property="name" column="role_name"/>
<result property="roleDec" column="role_dec"/>
<result property="status" column="role_status"/>
<result property="createDate" column="role_create_date"/>
<collection property="permissions"
resultMap="com.smart.mapper.PermissionMapper.BaseResultMap"/>
</resultMap>
<!-- 关联查询的时候 多表查询的字段不能有重复的 -->
<select id="selectList" resultMap="RolePermissionResultMap">
SELECT r.role_id,
r.name role_name,
r.role_dec,
r.create_date role_create_date,
r.status role_status,
p.per_id,
p.name,
p.per_dec,
p.create_date,
p.status
FROM t_role r
LEFT JOIN role_permission_relation rpr ON r.role_id = rpr.role_id
LEFT JOIN t_permission p ON p.per_id = rpr.per_id
WHERE r.status = 1
AND p.status = 1
</select>
</mapper>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
"http://ibatis.apache.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.smart.mapper.PermissionMapper">
<resultMap id="BaseResultMap" type="Permission">
<id property="perId" column="per_id"/>
<result property="name" column="name"/>
<result property="perDec" column="per_dec"/>
</resultMap>
</mapper>
六 注意
- 多表查询的时候如果查询的表的字段出现名字相同的时候,需要使用别名来解决数据错误的情况
动态SQL
1 if
使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。比如:
<select id="findActiveBlogWithTitleLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
这条语句提供了可选的查找文本功能。如果不传入 “title”,那么所有处于 “ACTIVE” 状态的 BLOG 都会返回;
如果传入了 “title” 参数,那么就会对 “title” 一列进行模糊查找并返回对应的 BLOG 结果
如果希望通过 “title” 和 “author” 两个参数进行可选搜索该怎么办呢?首先,我想先将语句名称修改成更名副其实的名称;接下来,只需要加入另一个条件即可。
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>
2 choose、when、otherwise
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
还是上面的例子,但是策略变为:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG(这可能是管理员认为,与其返回大量的无意义随机 Blog,还不如返回一些由管理员精选的 Blog)。
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
3 trim、where、set
前面几个例子已经方便地解决了一个臭名昭著的动态 SQL 问题。现在回到之前的 “if” 示例,这次我们将 “state = ‘ACTIVE’” 设置成动态条件,看看会发生什么,用where标签解决下面问题
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>
如果没有匹配的条件会怎么样?最终这条 SQL 会变成这样:
SELECT * FROM BLOG
WHERE
这会导致查询失败。如果匹配的只是第二个条件又会怎样?这条 SQL 会是这样:
SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’
这个查询也会失败。这个问题不能简单地用条件元素来解决。这个问题是如此的难以解决,以至于解决过的人不会再想碰到这种问题。
MyBatis 有一个简单且适合大多数场景的解决办法。而在其他场景中,可以对其进行自定义以符合需求。而这,只需要一处简单的改动:
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除
<select id="selectList" resultMap="BaseResult">
select b.bid, b.name, b.image, b.banner_model, b.banner_type, b.banner_type_id, b.banner_rank, b.status from t_banner b
<where>
<if test="name!=null and name!=''">
b.name like concat('%',#{name},'%')
</if>
<if test="model!=null and model!=''">
and b.banner_model like concat('%',#{model},'%')
</if>
<if test="type!=null and type!=''">
and b.banner_type like concat('%',#{type},'%')
</if>
<if test="bstatus==1 or bstatus==0">
and b.status=#{bstatus}
</if>
</where>
</select>
比如,和 where 元素等价的自定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
来看看与 set 元素等价的自定义 trim 元素吧:
<trim prefix="SET" suffixOverrides=",">
...
</trim>
注意,我们覆盖了后缀值设置,并且自定义了前缀值。
4 foreach
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!
事务
一 Spring 事务
用户定义一系列SQL操作的序列,这些做操作要么一起执行成功要么一起执行失败
- 包含多条SQL语句
- 多条SQL语句中包含,增删改的操作
- 如果多条SQL语句都是查询的操作,可以不需要开启事务
特性
-
原子性(Atomic)
-
一致性(Consistent)
-
隔离性(Isolated)
-
读取未提交 未提交读(readuncommited)
-
读取已提交(针对更新操作) 提交读(readcommit)
-
可重复读(增加删除 多条记录) 重复读(repeatableread)
-
串行化(serializable)
-
-
持久性(durable)
二 概要
Spring对事务也进行了支持 底层AOP实现
分类:
- 编程事务 (不推荐)
- 声明事务 (注解+配置)
三 基础使用
导入相关依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.9</version>
</dependency>
<!--单元测试时测试控制层需要这个依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<!--时间工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.9</version>
</dependency>
配置
<?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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 注册事务管理类 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"
/>
<!-- 开启注解事务的支持 -->
<tx:annotation-driven/>
</beans>
业务层
@Transactional
@Service
public class OrderServiceImpl implements OrderService {
// 开启事务 @Transactional
@Transactional
@Override
public String createOrder() {
return null;
}
}
四 核心注解
作用
@Transactional 开启 Spring 事务支持
源码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;//设置隔离级别
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
String timeoutString() default "";
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
}
属性 | 说明 | |
---|---|---|
Isolation | 设置隔离(在开发中不要去设置隔离级别) mysql隔离 REPEATABLE_READ oracle READ_COMMITTED | |
propagation | 事务传播机制 | |
timeout | 事务的超时时间 默认 -1 推荐 3 单位是秒 | |
readOnly | 表示可读性事务(建议不要设置) | |
rollbackFor | 默认 RuntimeException 级别 |
propagation
事务的传播机制
@Transactional(propagation=Propagation.REQUIRED)
可选值
可选值 | 说明 | |
---|---|---|
REQUIRED | 如果当前方法有事务就加入事务,没有就开启事务 | |
SUPPORTS | 如果入口方法有事务,就加入事务,如果入口方法没事务则不开启事务 | |
MANDATORY | 入口方法必须有事务,如果没有事务直接抛异常 | |
PROPAGATION_REQUIRES_NEW | 不管入口方法有没有事务都单独开启事务 | |
NOT_SUPPORTED | 不开启事务 | |
NEVER | 必须在一个没有事务的方法中执行,否则直接抛异常 | |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作 |
五 事务失效场景
- 在业务层的实现类上使用(类上加注解)
- 方法必须是public声明
- AOP --> 反射 —> 获取所有公开的方法;
-
异常必须是RuntimException异常或者子类
-
springmvc 重复扫描也会导致事务失效
-
数据必须支持事务 innoDB引擎才支持事务 MyISAM引擎不支持事务
-
异常被你处理
六 如何使用
注册送积分
注册送积分
- 查询用户是否存在
- 保存用户信息
- 添加积分
用户表
create table member(
member_id bigint auto_increment primary key ,
/*unique 用户名是唯一的*/
username varchar(255) unique not null comment '用户名',
password varchar(128) not null comment '密码',
/*unique 手机号是唯一的 11位*/
phone varchar(11) unique not null comment '手机号',
/*unique 邮箱也是唯一的*/
email varchar(255) unique not null comment '邮箱',
create_date datetime default now() not null comment '注册时间',
/*on update now() 更新就修改为更新当前时间*/
update_date datetime default now() on update now() not null comment '最后更新时间',
status tinyint(1) default 1 not null comment '是否删除 1 正常 0删除',
head varchar(255) default '/static/image/default.png' not null comment '用户头像'
);
积分表
create table t_integral
(
integral_id int auto_increment primary key comment '主键',
type int not null comment '积分类型 1 表示新用户注册送积分 2 签到送积分',
score int not null comment '积分',
create_date datetime default now() not null comment '创建时间',
operation_type int not null comment '1表示添加积分 2表示消费积分',
expire_data datetime not null comment '积分过期时间',
member_id bigint not null comment '用户id',
status tinyint default 1 not null comment '1 正常 2 过期 3冻结'
);
MemberMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
"http://ibatis.apache.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.smart.tx.mapper.MemberMapper">
<resultMap id="BaseResultMap" type="Member">
<id property="memberId" column="member_id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="phone" column="phone"/>
<result property="email" column="email"/>
<result property="status" column="status"/>
</resultMap>
<select id="selectMember" resultMap="BaseResultMap">
SELECT member_id,
username,
password,
phone,
email,
status
FROM member
WHERE username = #{username}
OR phone = #{phone}
OR email = #{email}
</select>
<!--返回主键-->
<insert id="insert" keyProperty="member.memberId" useGeneratedKeys="true">
INSERT INTO member (username, password, phone, email)
VALUES (#{member.username}, #{member.password}, #{member.phone}, #{member.email})
</insert>
</mapper>
MemberMapper
public interface MemberMapper {
/**
* 影响的行数 int
* Entity
* List<Entity>
*
* @return
*/
List<Member> selectMember(@Param("username") String username, @Param("phone") String phone, @Param("email") String email);
int insert(@Param("member") Member member);
}
MemberServiceImpl
@Service
public class MemberServiceImpl implements MemberService {
@Resource
private IntegralService integralService;
@Resource
private MemberMapper memberMapper;
/**
* 判断会员是否存在
* 添加信息到会员
* 添加100积分
*
* @param params
* @return 请求参数类转化成持久化对象 BeanUtils.copyProperties
*/
@Transactional(rollbackFor = ServiceException.class)
@Override
public boolean register(MemberRequestParams params) {
List<Member> list = memberMapper.selectMember(params.getUsername(), params.getPhone(), params.getEmail());
if (list == null || list.size() == 0) {
Member member = new Member();
// source 有值对象 target 目标对象
BeanUtils.copyProperties(params, member);
member.setPassword(DigestUtils.md5DigestAsHex(member.getPassword().getBytes()));
int count = memberMapper.insert(member);
if (count > 0) {
//避免生产环境中一些未知因素
int num = integralService.addIntegral(member.getMemberId(), 100, 1, 1, DateUtil.nextMonth());
return true;
} else {
throw new ServiceException(404, "用户注册失败");
}
} else {
throw new ServiceException(40010, "账户已存在");
}
}
}
status为动态的动态sql
status不是动态的就直接where status=1,只显示没有删除的数据,即status为1的数据
<select id="selectList" resultMap="BaseResultMap">
select a.apply_id, a.username, a.key_type, a.phone, a.id_card, a.key_password from rp_key_apply a
where status=1
<if test="name!=null and name!=''">
and a.username like concat('%',#{name},'%')
</if>
<if test="type!=null and type!=''">
and a.key_type like concat('%',#{type},'%')
</if>
<if test="phone!=null and phone!=''">
and a.phone like concat('%',#{phone},'%')
</if>
</select>
如果status是多个状态,不只是删除和正常的状态,即status也是动态的,就用where标签解决
<select id="selectList" resultMap="BaseResult">
select b.bid, b.name, b.image, b.banner_model, b.banner_type, b.banner_type_id, b.banner_rank, b.status from t_banner b
<where>
<if test="name!=null and name!=''">
b.name like concat('%',#{name},'%')
</if>
<if test="model!=null and model!=''">
and b.banner_model like concat('%',#{model},'%')
</if>
<if test="type!=null and type!=''">
and b.banner_type like concat('%',#{type},'%')
</if>
<if test="status==1 or status==0">
and b.status=#{status}
</if>
</where>
</select>
购物车动态sql
CartMapper.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.smart.carts.mapper.CartMapper">
<resultMap id="BaseResultMap" type="Carts">
<!--@mbg.generated-->
<!--@Table t_carts-->
<id column="cart_id" jdbcType="BIGINT" property="cartId"/>
<result column="product_id" jdbcType="BIGINT" property="productId"/>
<result column="member_id" jdbcType="BIGINT" property="memberId"/>
<result column="quantity" jdbcType="INTEGER" property="quantity"/>
<result column="product_name" jdbcType="VARCHAR" property="productName"/>
<result column="product_price" jdbcType="DECIMAL" property="productPrice"/>
<result column="status" jdbcType="INTEGER" property="status"/>
<result column="create_date" jdbcType="TIMESTAMP" property="createDate"/>
</resultMap>
<select id="selectCartsByMemberId" resultMap="BaseResultMap">
SELECT c.cart_id,
c.product_id,
c.member_id,
c.quantity,
c.product_name,
c.product_price
FROM t_carts c
WHERE member_id = #{memberId}
AND product_id = #{productId}
AND status = 1
</select>
<insert id="insert" keyProperty="cartId" useGeneratedKeys="true">
INSERT INTO t_carts
<trim prefix=" (" suffix=")" suffixOverrides=",">
<if test="carts.memberId != null">
member_id,
</if>
<if test="carts.productId != null">
product_id,
</if>
<if test="carts.productName != null">
product_name,
</if>
<if test="carts.quantity != null">
quantity,
</if>
<if test="carts.productPrice != null">
product_price,
</if>
</trim>
<trim prefix="values(" suffix=")" suffixOverrides=",">
<if test="carts.memberId != null">
#{carts.memberId},
</if>
<if test="carts.productId != null">
#{carts.productId},
</if>
<if test="carts.productName != null">
#{carts.productName},
</if>
<if test="carts.quantity != null">
#{carts.quantity},
</if>
<if test="carts.productPrice != null">
#{carts.productPrice},
</if>
</trim>
</insert>
<update id="updateById">
UPDATE t_carts
<trim prefix="SET" suffixOverrides=",">
<if test="carts.productName != null">
product_name= #{carts.productName},
</if>
<if test="carts.productPrice != null">
product_price= #{carts.productPrice},
</if>
<if test="carts.quantity != null">
quantity= #{carts.quantity},
</if>
<if test="carts.status != null">
status=#{carts.status},
</if>
<if test="carts.memberId != null">
member_id=#{carts.memberId},
</if>
<if test="carts.productId != null">
product_id =#{carts.productId},
</if>
</trim>
WHERE cart_id = #{carts.cartId}
</update>
<!--批量删除-->
<update id="batch">
<!-- 更新 status -->
UPDATE t_carts
SET status=0
WHERE member_id = #{memberId}
AND status = 1
AND cart_id IN
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</update>
</mapper>
CartMapper
public interface CartMapper {
/**
* 判断用户购物车中是否存在该条记录
*/
Carts selectCartsByMemberId(@Param("memberId") Long memberId, @Param("productId") Long productId);
/**
* 编程的思想
*
* @param carts
* @return
*/
int insert(@Param("carts") Carts carts);
/**
* 必须有主键
*
* @param
* @return
*/
int updateById(@Param("carts") Carts carts);
批量删除
int batch(@Param("ids") List<Long> ids, @Param("memberId") Long memberId);
}
CartService
public interface CartService {
Boolean add(CartsRequestParams cartsRequestParams);
int batchDel(List<Long> ids, Long memberId);
}
CartServiceImpl
@Service
public class CartServiceImpl implements CartService {
@Resource
CartMapper cartMapper;
/**
* 10 布娃娃 10
*
* @param cartsRequestParams
* @return qo对象
* 转化成
* po对象 entity
*/
@Override
public Boolean add(CartsRequestParams cartsRequestParams) {
int count;
try {
/**
* 判断用户是否已添加到购物车 用户ID
*/
Carts carts = cartMapper.selectCartsByMemberId(cartsRequestParams.getMemberId(), cartsRequestParams.getProductId());
if (carts == null) {
//添加
carts = new Carts();
// Spring BeanUtils
BeanUtils.copyProperties(cartsRequestParams, carts);
count = cartMapper.insert(carts);
} else {
// 更新的操作 数量的计算 >1
int quantity = cartsRequestParams.getQuantity() > 1 ? cartsRequestParams.getQuantity() : 1;
carts.setQuantity(carts.getQuantity() + quantity);
count = cartMapper.updateById(carts);
}
} catch (Exception ex) {
throw new ServiceException(ResponseCode.SYS_ERROR);
}
return count > 0;
}
@Transactional(rollbackFor = RuntimeException.class)
@Override
public int batchDel(List<Long> ids, Long memberId) {
int batch = cartMapper.batch(ids, memberId);
if (batch == ids.size()) {
return batch;
} else {
throw new ServiceException(ResponseCode.ERROR);
}
}
}
CartController
/**
* servlet + jsp
* 关联查询操作 事务
* 数据安全 锁
* 事务
* 购物车功能
* 1. 分页显示购物信息 分页查询
* 2 删除购物车信息 更新
* 2 删除多条购物车信息 批量更新
* 4 修改购物车数量 修改
* 5 添加商品到购物车 存在在修改数量 不存在添加记录
*/
@RestController
@RequestMapping("/carts")
public class CartController {
/**
* 业务逻辑
*/
@Resource
private CartService cartService;
/**
* 添加购物车
*/
@PostMapping("/add")
public ResponseResult<Boolean> add(@RequestBody CartsRequestParams cartsRequestParams) {
Boolean isAdd = cartService.add(cartsRequestParams);
return ResponseResult.success(isAdd);
}
/**
* 保存删除 购物车ID
* 用户查看购物车信息
*/
@PostMapping("/del/batch/")
public ResponseResult<Integer> batchDel(@RequestParam List<Long> ids, @RequestParam Long memberId) {
return ResponseResult.success(cartService.batchDel(ids, memberId));
}
}
全局异常处理
异常处理范例
/**
* 1. 如何使用自定义异常
* 2. 全局异常如何处理
* 3. 状态码封装
*/
@Service
public class MemberServiceImpl implements MemberService {
@Override
public String register(String username, String password) {
// 第一步 判断用户名或者手机号
if (false) {
// 注册逻辑代码
} else {
throw new ServiceException(ResponseCode.ACCOUNT_IS_EXIST);
}
return null;
}
}
枚举类响应状态码
/**
* 响应状态码
*/
@Getter
public enum ResponseCode {
/*
* ===============通用状态码 ===============
*/
SUCCESS(200, "success"),
ERROR(404, "error"),
SYS_ERROR(40400, "sys error"),
/**
* 系统相关的
*/
/**
* 业务异常
* <p>
* 订单状态
* <p>
* 用户状态码
*/
// 用户状态码
ACCOUNT_IS_EXIST(40100, "username or phone is exist");
private int status;
private String msg;
ResponseCode(int status, String msg) {
this.status = status;
this.msg = msg;
}
}
统一结果集
/**
*
* 状态码跟提示信息进行封装
*/
@Data
@AllArgsConstructor//提供全参构造方法
@NoArgsConstructor//提供无参构造方法
@Builder
public class ResponseResult<T> {
private int status;
private String msg;
private T data;
/**
* 通用成功返回的数据
*
* @param data
* @param <T>
* @return
*/
public static <T> ResponseResult<T> success(T data) {
return success(data, ResponseCode.SUCCESS);
}
//泛型 静态方法前需要加<T>
public static <T> ResponseResult<T> success(T data, ResponseCode responseCode) {
return new ResponseResultBuilder<T>()
.msg(responseCode.getMsg())
.status(responseCode.getStatus())
.data(data)
.build();
}
public static <T> ResponseResult<T> error() {
return error(ResponseCode.ERROR);
}
public static <T> ResponseResult<T> error(ResponseCode responseCode) {
return error(responseCode.getStatus(), responseCode.getMsg());
}
public static <T> ResponseResult<T> error(int status, String msg) {
return new ResponseResultBuilder<T>()
.msg(msg)
.status(status)
.build();
}
}
无语义异常处理
@Getter
public class BaseException extends RuntimeException {
private ResponseCode responseCode;
/**
* 维护的角度
*
* @param responseCode
*/
public BaseException(ResponseCode responseCode) {
super(responseCode.getMsg());
this.responseCode = responseCode;
}
}
控制层异常处理,继承BaseException无语义的异常处理
public class ControllerException extends BaseException {
public ControllerException(ResponseCode responseCode) {
super(responseCode);
}
}
业务层异常处理,继承BaseException无语义的异常处理
public class ServiceException extends BaseException {
public ServiceException(ResponseCode responseCode) {
super(responseCode);
}
}
全局异常处理类
/**
* 先定义个全局异常处理类
* 在类上使用 @RestControllerAdvice
*
*/
@RestControllerAdvice
@Slf4j
public class GlobalHandlerException {
/**
* 接收所有的异常
* @return
*/
@ExceptionHandler(Exception.class)
public static ResponseResult<Object> handlerException(Exception ex) {
//打印异常信息,不然不会报错,也不知道是什么错误
log.error(ex.getMessage());
//如果是业务层的异常
if (ex instanceof ServiceException) {
ServiceException serviceException = (ServiceException) ex;
return ResponseResult.error(serviceException.getResponseCode());
}
return ResponseResult.error();
}
//只接收业务层的异常ServiceException.class,当然也可以定义一个只接收控制层或者其他层的异常
@ExceptionHandler(ServiceException.class)
public static ResponseResult<Object> handlerException(ServiceException ex) {
log.error(ex.getMessage());
return ResponseResult.error(ex.getResponseCode());
}
}
单表一对多
两级菜单
entity
@Data
public class Category {
private Integer cateId;
/**
* 分类名称
*/
private String name;
/**
* 小图片
*/
private String icon;
/**
* 跳转路径
*/
private String url;
/**
* 创建时间
*/
private Date createDate;
/**
* 1 表示 一级菜单 2 表示二级菜单
*/
private Integer level;
/**
* 父类ID 0表示父类菜单
*/
private Integer parentId;
/**
* 是否删除 1---->正常 0---->表示删除
*/
private Boolean status;
//自己加
private List<Category> subList;
}
CategoryMapper
public interface CategoryMapper {
List<Category> selectList();
}
CategoryMapper.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.smart.carts.mapper.CategoryMapper">
<resultMap id="BaseResultMap" type="com.smart.carts.entity.Category">
<id column="cate_id" jdbcType="INTEGER" property="cateId"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
<result column="icon" jdbcType="VARCHAR" property="icon"/>
<result column="url" jdbcType="VARCHAR" property="url"/>
<result column="create_date" jdbcType="TIMESTAMP" property="createDate"/>
<result column="level" jdbcType="INTEGER" property="level"/>
<result column="parent_id" jdbcType="INTEGER" property="parentId"/>
<result column="status" jdbcType="BOOLEAN" property="status"/>
</resultMap>
<!-- 坑 多表查询属性不能一样 -->
<resultMap id="CategoryListResultMap" type="com.smart.carts.entity.Category" extends="BaseResultMap">
<collection property="subList" resultMap="SubResultMap"/>
</resultMap>
<resultMap id="SubResultMap" type="com.smart.carts.entity.Category">
<id column="sub_cate_id" jdbcType="INTEGER" property="cateId"/>
<result column="sub_name" jdbcType="VARCHAR" property="name"/>
<result column="sub_icon" jdbcType="VARCHAR" property="icon"/>
<result column="sub_url" jdbcType="VARCHAR" property="url"/>
<result column="sub_create_date" jdbcType="TIMESTAMP" property="createDate"/>
<result column="sub_level" jdbcType="INTEGER" property="level"/>
<result column="sub_parent_id" jdbcType="INTEGER" property="parentId"/>
<result column="sub_status" jdbcType="BOOLEAN" property="status"/>
</resultMap>
<select id="selectList" resultMap="CategoryListResultMap">
SELECT c.cate_id,
c.name,
c.icon,
c.url,
c.create_date,
c.level,
c.parent_id,
c.status,
sub.cate_id sub_cate_id,
sub.name sub_name,
sub.icon sub_icon,
sub.url sub_url,
sub.create_date sub_create_date,
sub.level sub_level,
sub.parent_id sub_parent_id,
sub.status sub_status
FROM pms_category c
LEFT JOIN pms_category sub ON c.cate_id = sub.parent_id
WHERE c.parent_id = 0;
</select>
</mapper>
多级菜单
entity
@Data
public class Address {
/**
* 地区id
*/
private Long addressId;
/**
*
*/
private String name;
/**
* 创建时间
*/
private Date createDate;
/**
* 菜单级别 1表示一级菜单 2表示二级菜单
*/
private Integer level;
/**
* 父类id
*/
private Integer parentId;
/**
* 是否删除 1表示正常 0表示删除
*/
private Boolean status;
/**
*二级菜单
*/
private List<Address> secondList;
/**
*三级菜单
*/
private List<Address> thirdList;
/**
*四级菜单
*/
private List<Address> fourthList;
}
AddressMapper
public interface AddressMapper {
List<Address> selectList();
}
AddressMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
"http://ibatis.apache.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.smart.mapper.AddressMapper">
<resultMap id="BaseResultMap" type="Address">
<id property="addressId" column="address_id"/>
<result property="name" column="name"/>
<result property="status" column="status"/>
<collection property="secondList" resultMap="SecondBaseResultMap"/>
</resultMap>
<resultMap id="SecondBaseResultMap" type="Address">
<id property="addressId" column="second_id"/>
<result property="name" column="second_name"/>
<result property="status" column="second_status"/>
<collection property="thirdList" resultMap="ThirdBaseResultMap"/>
</resultMap>
<resultMap id="ThirdBaseResultMap" type="Address">
<id property="addressId" column="third_id"/>
<result property="name" column="third_name"/>
<result property="status" column="third_status"/>
<collection property="fourthList" resultMap="fourthBaseResultMap"/>
</resultMap>
<resultMap id="fourthBaseResultMap" type="Address">
<id property="addressId" column="fourth_id"/>
<result property="name" column="fourth_name"/>
<result property="status" column="fourth_status"/>
</resultMap>
<select id="selectList" resultMap="BaseResultMap">
SELECT a.address_id,
a.`name`,
a.`status`,
a2.address_id second_id,
a2.`name` second_name,
a2.`status` second_status,
a3.address_id third_id,
a3.`name` third_name,
a3.status third_status,
a4.address_id fourth_id,
a4.`name` fourth_name,
a4.status fourth_status
FROM address a
LEFT JOIN address a2 ON a.address_id = a2.parent_id
LEFT JOIN address a3 ON a2.`address_id` = a3.`parent_id`
LEFT JOIN address a4 ON a3.`address_id` = a4.`parent_id`
WHERE a.`parent_id` = 0;
</select>
</mapper>
result column=“sub_create_date” jdbcType=“TIMESTAMP” property=“createDate”/>
<select id="selectList" resultMap="CategoryListResultMap">
SELECT c.cate_id,
c.name,
c.icon,
c.url,
c.create_date,
c.level,
c.parent_id,
c.status,
sub.cate_id sub_cate_id,
sub.name sub_name,
sub.icon sub_icon,
sub.url sub_url,
sub.create_date sub_create_date,
sub.level sub_level,
sub.parent_id sub_parent_id,
sub.status sub_status
FROM pms_category c
LEFT JOIN pms_category sub ON c.cate_id = sub.parent_id
WHERE c.parent_id = 0;
</select>
```
多级菜单
entity
@Data
public class Address {
/**
* 地区id
*/
private Long addressId;
/**
*
*/
private String name;
/**
* 创建时间
*/
private Date createDate;
/**
* 菜单级别 1表示一级菜单 2表示二级菜单
*/
private Integer level;
/**
* 父类id
*/
private Integer parentId;
/**
* 是否删除 1表示正常 0表示删除
*/
private Boolean status;
/**
*二级菜单
*/
private List<Address> secondList;
/**
*三级菜单
*/
private List<Address> thirdList;
/**
*四级菜单
*/
private List<Address> fourthList;
}
AddressMapper
public interface AddressMapper {
List<Address> selectList();
}
AddressMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
"http://ibatis.apache.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.smart.mapper.AddressMapper">
<resultMap id="BaseResultMap" type="Address">
<id property="addressId" column="address_id"/>
<result property="name" column="name"/>
<result property="status" column="status"/>
<collection property="secondList" resultMap="SecondBaseResultMap"/>
</resultMap>
<resultMap id="SecondBaseResultMap" type="Address">
<id property="addressId" column="second_id"/>
<result property="name" column="second_name"/>
<result property="status" column="second_status"/>
<collection property="thirdList" resultMap="ThirdBaseResultMap"/>
</resultMap>
<resultMap id="ThirdBaseResultMap" type="Address">
<id property="addressId" column="third_id"/>
<result property="name" column="third_name"/>
<result property="status" column="third_status"/>
<collection property="fourthList" resultMap="fourthBaseResultMap"/>
</resultMap>
<resultMap id="fourthBaseResultMap" type="Address">
<id property="addressId" column="fourth_id"/>
<result property="name" column="fourth_name"/>
<result property="status" column="fourth_status"/>
</resultMap>
<select id="selectList" resultMap="BaseResultMap">
SELECT a.address_id,
a.`name`,
a.`status`,
a2.address_id second_id,
a2.`name` second_name,
a2.`status` second_status,
a3.address_id third_id,
a3.`name` third_name,
a3.status third_status,
a4.address_id fourth_id,
a4.`name` fourth_name,
a4.status fourth_status
FROM address a
LEFT JOIN address a2 ON a.address_id = a2.parent_id
LEFT JOIN address a3 ON a2.`address_id` = a3.`parent_id`
LEFT JOIN address a4 ON a3.`address_id` = a4.`parent_id`
WHERE a.`parent_id` = 0;
</select>
</mapper>