该笔记包含了Maven,Spring Boot,MySQL和MyBatis的相关内容,为笔者学习黑马程序员Web开发时做的笔记,便于知识遗漏时能及时查看查漏补缺。如果能帮助到您,那便更好了。
管理和构建Java项目的开源工具
- 通过Maven自带的各种插件完成项目的标准化构建与各种文件的生成和管理
- 项目对象模型POM(Porject Object Model),Maven的核心思想。通过pom.xml中配置的信息来描述Java工程
- pom.xml中可添加依赖信息来进行依赖管理(pom.xml中配置的依赖会先在本地仓库中寻找,本地仓库中无就从中央仓库或者远程/私有仓库拉取)
<!-- 所有的依赖都在pom.xml中配置 -->
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId> 项目依赖的组织的唯一标识符
<artifactId>logback-classic</artifactId> 项目依赖的模块或库的名称
<version>1.4.5</version> 依赖的具体版本
</dependency>
</dependencies>
不知道groupId和version可在https://mvnrepository.com/中查看(version可选择Usage最高的版本)
从Spring Boot开始,并贯穿始终
创建好一个SpringBoot项目后,会自动生成一个启动类
。要想启动SpringBoot项目就直接运行该启动类
package com.zzy.springtest01;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
// 启动类——用于启动SpringBoot工程
@SpringBootApplication
public class SpringTest01Application {
public static void main(String[] args) {
SpringApplication.run(SpringTest01Application.class, args);
}
}
接下来我们在/com.zzy.springtest01
目录下连包带类创建controller.HelloController
,用于处理浏览器请求。
当浏览器url输入localhost:8080/response(SpringBoot项目默认占用8080端口)后,HelloController
能返回给浏览器一段信息。
controller
层的核心功能就是接收浏览器请求,并响应
package com.zzy.springtest01.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 请求处理类
*/
@RestController
public class HelloController {
@RequestMapping("/response")
public String response() {
String res = "进行请求处理...";
System.out.println(res);
return res;
}
}
@RestController
:指定该类为Spring的请求处理类/控制器类,用于处理HTTP请求,并返回数据而非视图
@RequestMapping(/response)
:表示当浏览器访问/response时,就会调用该函数
程序员直接解析请求的内容,从字符串中提取出请求行、请求头、请求体,以及在输出流中输出响应行、响应头和响应体是非常麻烦的。
很多公司开发了专门的Web服务器来进行HTTP请求/响应解析,如Apache Tomcat
Tomcat是一个Web服务器,也叫Servlet容器
Web服务器是一个软件,对HTTP协议的操作进行封装,使得程序员不必直接对协议进行操作
我们只需在自己的服务器中安装一个Web服务器(如Tomcat),将开发好的应用直接部署在Tomcat上
将自己的程序资源(比如myApp文件夹)拷贝到Tomcat下的webapps文件夹下就可以了。浏览器访问localhost:8080/myApp(Tomcat默认占用8080端口,Java有时也占用8080端口,易冲突)
SpringBoot中开发web项目都要引入spring-boot-starter-web
依赖,这一依赖内嵌了Tomcat服务器,并占用8080端口(就不用自己本地下载安装Tomcat了)(注:spring-boot-starter
称为起步依赖,是每个特定项目都要引入的依赖。pom.xml中不会注明版本,因为这些依赖都是交由父工程由Spring团队统一管理)
<!-- 该项目的父工程 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- 每个版本的父工程都指定了各起步依赖的版本信息,无需自己操心 -->
浏览器GET和POST请求,以及对请求的响应
Tomcat
也被称为Servlet容器
SpringBoot
提供了一项非常核心的程序:DispatcherServlet类
,也被称为核心控制器
或者前端控制器
它将浏览器的HTTP请求封装到HttpServletRequest类
中,将返回给浏览器的数据封装到HttpServletResponse类
中,简化了HTTP协议的使用
接口测试工具。缺少前端时,后端开发人员每开发完一个接口,就可以用Postman进行测试
Postman实质上模拟Chrome,提出GET或者POST请求,并接受返回数据
!!!注意:浏览器地址栏的请求都是GET请求
原始方式:通过
HttpServletRequest
对象获取请求参数
package com.zzy.springtest01.controller;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GetRequestParams {
@RequestMapping("/params")
public String getRequestParams(HttpServletRequest httpServletRequest){ // 声明一个HttpServletRequest对象来接受浏览器请求报文
String name = httpServletRequest.getParameter("name"); // 调用HttpServletRequest对象的getParameter()返回请求的参数(String型)
String age = httpServletRequest.getParameter("age");
String str = "获取到数据 name="+name+" age="+age;
System.out.println(str);
return str;
}
}
// Postman GET请求:http://localhost:8080/params?name=Tom&age=21
基于
SpringBoot
的接收方式:要求形参中的参数名称(name, age)要和请求的参数名称(name, age)一致,SpringBoot才会一一对应
// 基于SpringBoot来获取请求参数
@RequestMapping("/params")
public String getRequestParams(String name, Integer age){ // 直接在方法形参中指定各参数,SpringBoot会自动进行类型转换
String str = "name = "+name+" age = "+age;
System.out.println(str);
return str;
}
// Postman GET请求:http://localhost:8080/params?name=Tom&age=21
当使用基于SpringBoot的接收方式接收简单参数时,参数数量较多不方便,则可用实体类来接收参数
实体类: Java中不包含业务逻辑的单纯用来存储数据的 java类,定义了一系列存储数据的属性以及简单的
set(), get(),toString()
方法
com.zzy.springtest01
|
|
controller
| |
| RequestHandler.java
|
pojo
|
User.java
// User.java
// pojo包下存放实体类
public class User {
private String name; // !!!注意,实体类的属性取名要和请求的参数的名称一样,Spring才能一一对应
private Integer age;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}
// RequestHandler.java
package com.zzy.springtest01.controller;
import com.zzy.springtest01.pojo.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 请求处理类
*/
@RestController
public class RequestHandle {
@RequestMapping("/getParamsPOJO")
public String getParamsPOJO(User user){ // 直接用实体类来接收请求参数
System.out.println(user);
return user.toString();
}
}
// Postman GET http://localhost:8080/getParamsPOJO?name=Tom&age=21
在表单中,我们常会遇到多选框,如hobby栏可以多选game, drama, running等等。在GET请求时,请求参数会是:
hobby=game&hobby=drama&hobby=running
。这时,就可以使用数组来接收参数,或者使用集合来接收参数
// 使用数组来接收参数
// Postman GET http://localhost:8080/arrayParams?hobby=java&hobby=python&hobby=coding&hobby=exercising
// 通过数组来接收相同的参数
@RequestMapping("/arrayParams")
public String arrayParams(String[] hobby){ // 注意方法中形参的名称必须和请求参数名称一致,Spring才能识别
String str = Arrays.toString(hobby); // 数组转字符串的方法
System.out.println(str);
return str;
}
// 使用集合来接收参数
// 将方法的形参改为:
@RequestParam List<String> hobby
// !!!注意
// @RequestParam用于绑定请求参数到形参hobby
// 形参的集合名称(hobby)要和请求参数的名称(hobby)一致
有时我们前端会提交时间信息给服务器,时间的格式多种多样
// 接收时间参数
// Postman GET http://localhost:8080/dateParams?paramDate=2023-11-01 20:05:00
@RequestMapping("/dateParams")
public String dateParams(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")LocalDateTime paramDate){
String str = paramDate.toString();
System.out.println(str);
return str;
}
// @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")指定了时间的格式
// LocalDateTime是一个官方的java类
当浏览器发出json类型的请求参数时,使用的是
POST
请求,请求参数放在请求体(RequestBody)中
// Postman POST
/*
{
"name":"Tom",
"age":21,
"address":{
"province":"Sichuan",
"city":"Chengdu"
}
}
*/
// pojo包下
// Address.java
public class Address{
private String province;
private String city;
setter();
getter();
toString();
}
// Person.java
public class Person{
private String name;
private Integer age;
private Address address;
setter();
getter();
toString();
}
// controller包下
...
// 接收JSON格式数据
@RequestMapping("/jsonParam")
public String jsonParam(@RequestBody Person person){
String str = person.toString();
System.out.println(person);
return str;
}
...
有时客户端url地址http://localhost:8080/pathParam/
1~...
,这里参数变成了url地址的一部分,如何获取到这样的参数呢?
// 接受路径参数 http://localhost:8080/pathParam/1
@RequestMapping("/pathParam/{id}") // {id}表示id不是固定值
public String pathParam(@PathVariable Integer id){ // 指定id是一个PathVariable
System.out.println(id);
return id.toString();
}
// 接受多个路径参数 http://localhost:8080/pathParam/1/Tom
@RequestMapping("/pathParam/{id}/{name}")
public String pathParam(@PathVariable Integer id, @PathVariable String name){ // 指定id是一个PathVariable
System.out.println(id+name);
return "OK";
}
!!!注意:我们上面写的每一个方法都是一个功能接口
接收完各式各样的请求数据后,我们来设置响应结果
响应依赖于@ResponseBody
注解。前8小节我们接收到浏览器请求数据后都给了浏览器响应,但好像没有用到@ResponseBody
?
/**
* 创建一个统一响应结果封装类Result.java(可放在pojo包下)
*/
public class Result {
private Integer code ;//1 成功 , 0 失败
private String msg; //提示信息
private Object data; //响应的数据 data
public Result() { // 无参构造方法
}
public Result(Integer code, String msg, Object data) { // 有参构造方法
this.code = code;
this.msg = msg;
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static Result success(Object data){ // success的静态方法(带返回值)
return new Result(1, "success", data);
}
public static Result success(){ // success的静态方法(无返回值)
return new Result(1, "success", null);
}
public static Result error(String msg){ // error的静态方法
return new Result(0, msg, null);
}
@Override
public String toString() {
return "Result{" +
"code=" + code +
", msg='" + msg + ''' +
", data=" + data +
'}';
}
}
// 这样就可以将响应结果统一
// 接收时间参数
@RequestMapping("/dateParams")
public Result dateParams(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")LocalDateTime paramDate){
return Result.success(paramDate);
}
// 接收JSON格式数据
@RequestMapping("/jsonParam")
public Result jsonParam(@RequestBody Person person){
return Result.success(person);
}
// 接受路径参数
@RequestMapping("/pathParam/{id}")
public Result pathParam(@PathVariable Integer id){
return Result.success(id);
}
前端发起GET请求,请求获取员工信息。后端读取员工信息emp.xml中的数据,修改gender为“男”/“女”,job为“讲师”,“班主任”,“就业指导”,将最终结果返回给前端。
<!-- emp.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<emps>
<emp>
<name>金毛狮王</name>
<age>55</age>
<image>https://web-framework.oss-cn-hangzhou.aliyuncs.com/web/1.jpg</image>
<!-- 1: 男, 2: 女 -->
<gender>1</gender>
<!-- 1: 讲师, 2: 班主任 , 3: 就业指导 -->
<job>1</job>
</emp>
<emp>
<name>白眉鹰王</name>
<age>65</age>
<image>https://web-framework.oss-cn-hangzhou.aliyuncs.com/web/2.jpg</image>
<gender>1</gender>
<job>1</job>
</emp>
<emp>
<name>青翼蝠王</name>
<age>45</age>
<image>https://web-framework.oss-cn-hangzhou.aliyuncs.com/web/3.jpg</image>
<gender>1</gender>
<job>2</job>
</emp>
<emp>
<name>紫衫龙王</name>
<age>38</age>
<image>https://web-framework.oss-cn-hangzhou.aliyuncs.com/web/4.jpg</image>
<gender>2</gender>
<job>3</job>
</emp>
</emps>
// 查看前端请求的路径
axios.get('/listEmp').then(res=>{
if(res.data.code){
this.tableData = res.data.data;
}
});
//前端请求路径为 /listEmp
// empController.java
package com.zzy.springtest01.controller;
import com.zzy.springtest01.pojo.Emp;
import com.zzy.springtest01.pojo.Result;
import com.zzy.springtest01.utils.XmlParserUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class EmpController {
@RequestMapping("/listEmp")
public Result list() {
// 1. 加载并解析emp.xml
String file = this.getClass().getClassLoader().getResource("emp.xml").getFile(); // 动态加载文件
System.out.println(file);
List<Emp> empList = XmlParserUtils.parse(file, Emp.class); // 黑马提供的xml解析工具类
// 2. 对数据进行处修改(gender, job)
empList.stream().forEach(emp -> {
// 2.1 处理gender
if (emp.getGender().equals("1")) {
emp.setGender("男");
} else {
emp.setGender("女");
}
// 2.2 处理job
if (emp.getJob().equals("1")) {
emp.setJob("讲师");
} else if (emp.getJob().equals("2")) {
emp.setJob("班主任");
} else if (emp.getJob().equals("3")) {
emp.setJob("就业指导");
}
});
// 3. 响应请求
return Result.success(empList);
}
}
empList.stream().forEach( emp -> {......} );
这里用到了List
类中自带的遍历forEach()
方法。
forEach()
方法中使用了lambda表达式来编写匿名函数
forEach( emp -> { 对遍历的每个对象emp进行的操作 } )
!!!注意:前端项目可以放在
resource/static
目录下。后端启动后,前端可以访问浏览器localhost:8080/emp.html(无需加/static/)
为何引入三层架构?
简化接口的设计
便于接口维护与复用(比如说,目前我能够访问xml文件数据,但现在我想扩展功能:既能访问xml数据,又能访问云端数据,还能访问数据库中的数据,不分层的话,逻辑处理就会很复杂)
尽量使得各接口的职责单一
- controller层:接收请求,调用对应的数据访问与逻辑处理程序,最后响应请求
- service层:已得到要处理的数据,进行处理并返回处理后的数据
- dao层:只专注于如何获取数据
三层架构具体内容:
对响应案例进行修改:
将原接口的三大部分:数据访问、逻辑处理、响应请求分为三层
数据访问:
// dao层
// 先创建数据访问的接口,定义数据访问的接口方法。再对接口进行不同实现
/**
* dao层的接口
* 只关注如何加载数据
*/
public interface LoadData {
/**
* 加载数据
* @return 加载后的数据(原始数据)
*/
public List<Emp> empList();
}
// 再用不同方式实现这个接口的方法
// LoadDataV1.java:第一种实现数据访问接口的具体实现
public class LoadDataV1 implements LoadData {
@Override
public List<Emp> empList() {
String file = this.getClass().getClassLoader().getResource("emp.xml").getFile(); // 动态加载文件
System.out.println("file laods successfully: " + file);
List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
return empList;
}
}
逻辑处理:
// service层
// 先创建逻辑处理的接口,定义逻辑处理的接口方法。再对逻辑处理接口进行不同实现
/**
* 修改数据的接口
*/
public interface ModifyData {
/**
* 修改数据
* @return 返回修改好的数据
*/
public List<Emp> modifyData();
}
public class ModifyDataV1 implements ModifyData {
private LoadData loadData = new LoadDataV1(); // 获取加载数据的类
@Override
public List<Emp> modifyData() {
List<Emp> empList = loadData.empList(); // 获取加载好的数据
empList.stream().forEach(emp -> {
// 2.1 处理gender
if (emp.getGender().equals("1")) {
emp.setGender("男");
} else {
emp.setGender("女");
}
// 2.2 处理job
if (emp.getJob().equals("1")) {
emp.setJob("讲师");
} else if (emp.getJob().equals("2")) {
emp.setJob("班主任");
} else if (emp.getJob().equals("3")) {
emp.setJob("就业指导");
}
});
return empList;
}
}
响应请求:
@RestController
public class EmpController {
@RequestMapping("/listEmp")
public Result list() {
// 调用对应请求处理方法,响应请求
return Result.success(new ModifyDataV1().modifyData());
}
}
内聚: 软件中各个功能模块内部的功能联系
耦合: 衡量软件中各个层/模块之间的依赖、关联的程度
我们追求高内聚,低耦合
在上一节三层架构中,我们将代码分为dao层、service层、controller层。每一层都只专注于自己的功能。
在controller层中,我们new ModifyDataV1().modifyData()
,即创建了service层的一个对象,这称之为耦合。
如果我们想使用另一个业务逻辑,则需对service层进行修改,并且controller层创建的对象也要进行修改,不想这样,要对模块之间解耦。
Solution:
控制反转 inversion of control
依赖注入 dependency injection
Bean对象:容器中创建管理的对象
如何实现控制反转IOC和依赖注入DI:
控制反转:
service层的ModifyDataV1.java
依赖dao层的LoadDataV1.java
在LoadDataV1
类上加上@Component
注解,表示该类交由IOC容器管理
controller层的EmpController.java
依赖service层的ModifyDataV1.java
在ModifyDataV1
类上加上@Component
注解,表示该类交由IOC容器管理
依赖注入:
ModifyDataV1中声明LoadDataV1对象时,无需new,直接:
@Autowired
private LoadData obj; // IOC容器知道给obj赋为一个LoadDataV1对象
EmpController中声明ModifyData对象时,无需new,直接:
@Autowired
private ModifyData obj; // IOC容器知道给obj赋为一个ModifyDataV1对象
IOC & DI实现解耦了吗?
当前我的service层有一个实现:ModifyDataV1
类,我想创建一个新的实现ModifyDataV2
。只需取消ModifyDataV1
类上的@Component注解,在ModifyDataV2
类上加上@Component注解就行,这样controller层使用@Autowired声明ModifyData
对象时就知道,我们需要的是ModifyDataV2
注:Bean的声明
!!!注意:SQL语句记得加上
;
DataBase Definition Language
!!!注意:DataBase和Schema是一样的意思
MySQL支持3大类数据类型
数值类型
字符串类型
日期时间类型
DataBase Manipulate Language
对数据库进行增删改功能
DataBase Query Language 多表操作 使用频率最高
聚合函数:不对
null
值进行统计
# 统计个数
# count(*)写法——推荐(MySQL底层有针对性优化)
select count(*) from tb_emp;
# count(字段)——会自动忽略null
select count(id) from tb_emp;
分组查询
分组查询返回的表的字段主要有两类:
- 分组字段(分组的依据)
- 聚合函数(如统计总数)
# 先查询入职时间在‘2015-01-01’之前的员工,并对结果根据职位分组,获取员工数量大于等于2的职位
select job, count(*) from tb_emp where entryDate <= '2015-01-01' group by job having count(*) >= 2;
面试题where
和having
后面都跟的是条件,这两个条件有什么区别?
where
是分组之前进行过滤,不符合where
条件的不参加过滤having
是分组之后再进行的一次过滤where
不能对聚合函数进行判断,而having
可以数据 -> where条件 --(符合where条件的数据)–>分组 --> having条件 --> 最终结果
起始索引的公式:(页码 - 1)* 每页展示的记录数
if(查询条件, true时取值, false时取值)
select if(gender = 1,'男','女') as '员工性别', count(*)
from tb_emp
group by gender;
case 表达式
when 值1 then 结果1
when 值2 then 结果2
...
else 结果n
end
select case job
when 1 then '班主任'
when 2 then '讲师'
when 3 then '学工主管'
when 4 then '教研主管'
end as '职位'
, count(*) as '人数'
from tb_emp
group by job;
如有一张部门表和一张员工表
部门表记录了部门id和对应的部门名称
员工表记录了员工的各种信息和所属部门
分析知,部门表为主表
如何在数据库层面将这两张表关联起来?
直接select * from tb1,tb2
会将两张表做笛卡尔积:
添加where
限制条件能消除笛卡尔积
多表查询主要内容:
隐式内连接:
显式内连接:
左外连接:
右外连接:
左右外连接可以相互转换,项目中一般使用左外连接
将查询合理地分解为多步
标量子查询: 返回的是单个数据
列子查询: 返回的是一列多行的数据
行子查询: 返回的是一行多列的数据
表子查询: 返回的是一个临时的表
场景: 对多表进行操作时,需要格外关注数据一致性的问题
如何解决数据一致性问题?
start transaction; # 开启事务
delete from tb_dept where id = 1;
delete from tb_emp where dept_id = 1;
# 若所有的SQL语句执行成功,则:
commit; # 提交事务(需要提交给数据库服务器)
# 若有SQL执行失败,则手动回滚:
rollback; # 回滚数据
事务的四大特性ACID: 面试题
数据库优化策略之一
当数据库表的数据很多时,查询会明显耗费更多时间
对600 0000条数据进行select
不用索引:13s
使用索引:4ms
优秀的持久层/数据访问层/dao/(Mybatis中叫Mapper)框架,用于简化JDBC的开发
准备工作:
创建springboot工程,引入Mybatis相关依赖
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
创建数据库表user
创建实体类User(实体类的属性和数据库表一一对应,建议类型都用封装类型,如int->Integer)
/**
* 与test04数据库中的user表对应
*/
public class User {
private Integer id;
private String name;
private Short age;
private Short gender;
private String phone;
// constructors
public User() {} // 无参构造
public User(Integer id, String name, Short age, Short gender, String phone) {...} // 有参构造
// setters and getters
...
// toString
@Override
public String toString() {...}
}
配置数据库连接(4点,在springboot的application.properties)
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
(/后为服务器地址,若本机作为数据库服务器,则用localhost:3306;mybatis为数据库的名称)spring.datasource.username=root
spring.datasource.password=123456
编写SQL语句(注解/XML)
编写SQL语句可采用
注解@
或XML映射文件
的形式
在Mybatis中,创建一个package名为mapper(相当于dao数据访问层,只不过mapper层专门访问数据库)
在Mapper中创建一个访问user表的接口UserMapper
/**
* 与数据库中的user表交互
*/
@Mapper // 将该接口交给Mybatis管理,运行时会自动生成相应对象,并交给IOC容器管理,使用时直接@Autowired来创建对象
public interface UserMapper {
@Select("select * from user")
public List<User> list();
}
访问数据库user表时:
@Autowired
private UserMapper userMapper; // 先Autowired一个UserMapper对象
// 然后再获取到数据库操作结果:
public void testUserMapperlist(){
List<User> userlist = userMapper.list();
userlist.forEach(user -> {
System.out.println(user);
});
}
简化实体类的编写,无需再手写setter, getter, toString, constructors,只需使用几个注解
可配置Mybatis的日志信息,并指定在控制台输出
# application.properties # 配置Mybatis日志,并指定输出到控制台中 mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
@Mapper // 将该接口交给Mybatis管理,运行时会自动生成相应对象,并交给IOC容器管理,使用时直接@Autowired来创建对象
public interface EmpMapper {
/**
* 选择所有的员工信息
* @return
*/
@Select("select * from emp")
public List<Emp> list();
}
@Mapper // 将该接口交给Mybatis管理,运行时会自动生成相应对象,并交给IOC容器管理,使用时直接@Autowired来创建对象
public interface EmpMapper {
/**
* 根据id删除某员工
*/
@Delete("delete from emp where id = #{id}")
public void deleteEmp(Integer id); // @Delete其实有返回值,会返回操作(删除)了多少条数据,但一般就用void
}
#{}
为SQL的占位符,使用${}
也可以,但是存在SQL注入
的问题。所以一般若需给SQL语句传参,都使用#{}
,它会在编译时生成预编译的SQL,更高效也更安全面试题区别:
delete from emp where id = #{id} id=3
- 编译后会生成预编译语句:
delete from emp where id = ? parameters = 3
delete from emp where id = ${id} id=3
- 编译后会生成:
delete from emp where id=3
@Mapper // 将该接口交给Mybatis管理,运行时会自动生成相应对象,并交给IOC容器管理,使用时直接@Autowired来创建对象
public interface EmpMapper {
/**
* 插入一条员工信息
* @param emp
*/
@Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) value (#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{dept_id},#{create_time},#{update_time})")
public void insertEmp(Emp emp); // #{}内的内容是Emp对象的属性名
}
主键返回
有些应用场景需要我们获取到刚刚插入的数据的主键值。比如维护中间关系表时。
常见的应用场景为弹出一个表单,用于修改当前已有的数据。
@Mapper // 将该接口交给Mybatis管理,运行时会自动生成相应对象,并交给IOC容器管理,使用时直接@Autowired来创建对象
public interface EmpMapper {
/**
* 传入更新后的Emp对象,对数据库中相应id的元组进行更新
* @param emp
*/
@Update("update emp set username=#{username}, name=#{name},gender=#{gender},image=#{image},job=#{job},entrydate=#{entrydate},dept_id=#{dept_id},update_time=#{update_time} where id=#{id}")
public void updateEmp(Emp emp);
}
@Mapper // 将该接口交给Mybatis管理,运行时会自动生成相应对象,并交给IOC容器管理,使用时直接@Autowired来创建对象
public interface EmpMapper {
/**
* 根据id查询员工信息
* @param id
*/
@Select("select * from emp where id=#{id}")
public Emp queryEmpById(Integer id);
}
查询时,有时会出现查询到的字段值为
null
的情况原因:当字段名和属性名一致时,Mybatis会自动封装;当不一致时,Mybatis无法自动封装
解决办法:
方案一:在SQL语句中,给字段名和属性名不一致的字段起别名
@Select("select dept_id deptId, update_time updateTime from emp where id=#{id}") // 其中dept_id, update_time为数据库中的字段名,deptId, updateTime为封装的Emp类中的属性名
方案二:通过
@Results
,@Result
注解进行手动映射(麻烦不推荐)@Results({ @Result(column="dept_id", property="deptId"), @Result(column="update_time", property="updateTime") }) @Select("select * from emp where id=#{id}")
方案三:开启Mybatis的下划线命名法<->驼峰命名法自动映射开关
// application.properties mybatis.configuration.map-underscore-to-camel-case=true
使用Mybatis在Java中编写SQL语句,既可以采用注解@的形式,也可采用XM了映射文件的形式
XML映射文件更适用于较复杂的SQL语句的编写
定义XML映射文件
建好EmpMapper.xml
建好xml映射文件的结构:
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zzy.springtest01.mapper.EmpMapper" >
...
</mapper>
编写SQL语句
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zzy.springtest01.mapper.EmpMapper">
<insert id="insertEmp">
insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) value (#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{dept_id},#{create_time},#{update_time})
</insert>
<update id="updateEmp">
update emp set username=#{username}, name=#{name},gender=#{gender},image=#{image},job=#{job},entrydate=#{entrydate},dept_id=#{dept_id},update_time=#{update_time} where id=#{id}
</update>
<select id="list" resultType="com.zzy.springtest01.pojo.Emp">
select *
from emp;
</select>
<delete id="deleteEmp">
delete
from emp
where id = #{id}
</delete>
</mapper>
随着用户的输入或外部条件的变化而变化的SQL语句称为动态SQL
原:
<select id="listSomeEmp" resultType="com.zzy.springtest01.pojo.Emp">
select *
from emp
where
name like concat('%', #{name}, '%')
and gender = #{gender}
and entrydate between #{begin} and #{end}
order by update_time desc;
</select>
现:
<select id="listSomeEmp" resultType="com.zzy.springtest01.pojo.Emp">
select *
from emp
<where> // <where>标签能自动判断<if>标签中的条件并相应地生成SQL语句,并且删掉多余的"and"
<if test="name!=null"> // test属性中填条件
name like concat('%', #{name}, '%')
</if>
<if test="gender!=null">
and gender = #{gender}
</if>
<if test="begin!=null and end!=null">
and entrydate between #{begin} and #{end}
</if>
</where>
order by update_time desc;
</select>
标签用于:
select * from emp where...
的where条件全为null,则不生成where关键字---->select * from emp
,
等符号标签用于update语句:
自动删除多余的,
等符号
原:
<update id="updateEmp">
update emp
set
<if test="username!=null">
username=#{username},
</if>
<if test="name!=null">
name=#{name},
</if>
<if test="gender!=null">
gender=#{gender},
</if>
<if test="image!=null">
image=#{image},
</if>
<if test="job!=null">
job=#{job},
</if>
<if test="entrydate!=null">
entrydate=#{entrydate},
</if>
<if test="dept_id!=null">
dept_id=#{dept_id},
</if>
<if test="update_time!=null">
update_time=#{update_time}
</if>
where id = #{id}
</update>
现:
<update id="updateEmp">
update emp
<set>
<if test="username!=null">
username=#{username},
</if>
<if test="name!=null">
name=#{name},
</if>
<if test="gender!=null">
gender=#{gender},
</if>
<if test="image!=null">
image=#{image},
</if>
<if test="job!=null">
job=#{job},
</if>
<if test="entrydate!=null">
entrydate=#{entrydate},
</if>
<if test="dept_id!=null">
dept_id=#{dept_id},
</if>
<if test="update_time!=null">
update_time=#{update_time}
</if>
</set>
where id = #{id}
</update>
<!-- delete from emp where id in (15,16,17) -->
<delete id="deleteInBulk">
delete
from emp
where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
属性:
在真实的应用场景中,不推荐使用select *
,查询效率低下,而是建议将要查询的所有字段都显式地写出来
这样在xml映射文件中就会出现大量重复的例如:select id, username, name, gender, entrydate, job,... from emp
等语句
能否将这些重复的部分给封装起来,以便复用?
SQL抽取:
<sql id="CommonSelect">
select id, username, name, gender, entrydate, job,... from emp
</sql>
include:
<include refid="CommonSelect" />
例子:
<mapper ... >
<sql id="CommonSelect">
select id, username, name, gender, entrydate, job,... from emp
</sql>
<select id="..." resultType="...">
<include refid="CommonSelect"/>
<where>
<if test="name!=null">
name like concat('%', #{name}, '%')
</if>
<if test="gender!=null">
and gender = #{gender}
</if>
<if test="begin!=null and end!=null">
and entrydate between #{begin} and #{end}
</if>
</where>
order by update_time desc;
</select>
</mapper>
文章浏览阅读1.9w次,点赞7次,收藏62次。TCGA数据下载和整理的网站及软件发表很多了,比如Broad GDAC Firehose, Oncomine, TCGAbiolinks,TCGA-Assembler, TCGA2STAT,RTCGAToolbox等等,这些网站或软件要么使用的是TCGA更新前的数据,要么运行起来比较繁琐。当然各个工具都有其优势所在。之前在论坛里分享了自己下载和整理TCGA数据的Python代码。最近忙里偷_gdcrnatools软件包进行差异基因分析
文章浏览阅读1.9k次。工具/原料硬件:计算机操作系统:Windows7方法/步骤1.Windows7系统不能更改日期和时间的解决方法2.在本地组策略编辑器窗口,展开Windows设置 - 安全设置 - 本地策略;3.在本地策略中找到:用户权限分配,左键点击:用户权限分配,在用户权限分配对应的右侧窗口找到:更改系统时间,并左键双击:更改系统时间;4.在打开的更改系统时间 属性窗口,我们点击:添加用户或组(U);5.在选择..._win7系统时间老是自己跳变
文章浏览阅读1k次。一、ORM 模型介绍1 、 ORM 模型对象关系映射(英语:(Object Relational Mapping,简称ORM,或ORM,或OR mapping),是一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。面向对象是从软件工程基本原则(如耦合、聚合、封装)的基础上发展起来的,而关系数据库则是从数学理论发展而来的,两套理论存在显著的区别。为了解决这个不匹配的现象,对象关系映射技术应运而生。对象关系映射(Object-Relational Mapping)提供了概念性的、_pycharm怎么创建orm模型
文章浏览阅读250次。球机摄像头:球机为一体化设备,可以通过云台控制进行转动、变倍和自动聚焦等操作,若需要对设备周边切换场景监控,如大门口、户外活动场所等,可以选择球机。_前端摄像头的选型依据
文章浏览阅读177次。前面的话 正则表达式是用于描述字符排列和匹配模式的一种语法规则。它主要用于字符串的模式分割、匹配、查找及替换操作。在PHP中,正则表达式一般是由正规字符和一些特殊字符(类似于通配符)联合构成的一个文本模式的程序性描述。正则表达式有三个作用:1、匹配,也常常用于从字符串中析取信息;2、用新文本代替匹配文本;3、将一个字符串拆分为一组更小的信息块。本文将详细介绍PHP中的正则表达式基础语法 _正则表达式主要用于字符串模式匹配或字符串匹配,即什么操作
文章浏览阅读415次。很多小伙伴都遇到过win7系统打开计算机未响应的困惑吧,一些朋友看过网上零散的win7系统打开计算机未响应的处理方法,并没有完完全全明白win7系统打开计算机未响应是如何解决的,今天小编准备了简单的解决办法,只需要按照1、点击win7 32位旗舰版系统电脑的开始菜单,打开控制面板; 2、在控制面板中选择外观和个性化;的顺序即可轻松解决,具体的win7系统打开计算机未响应教程就在下文,一起来看看吧!..._电脑打开计算机未响应
文章浏览阅读1.2k次。电脑不想被他人乱动,来设置下BIOS管理员密码和开机密码,就让学习啦小编来告诉大家通过bios怎么设置开机密码的方法吧,希望对大家有所帮助。通过bios设置开机密码方法计算机开机以后,按键盘的Delete键进入BIOS的设置画面,如下图所示。因为开机可以按Delete键进入设置画面的时间很短,您可以在计算机一开机就慢慢的重复按Delete键,以免错过进入设置画面又要重新再开机。按键盘向下箭头键移到..._bios开机密码 画面
文章浏览阅读1.6w次,点赞14次,收藏45次。已收藏下面这个链接的方法也不错excel批处理技巧:如何制作文件档案管理系统excel批处理技巧:如何制作文件档案管理系统http://www.360doc.com/content/18/0913/13/18781560_786337463.shtml有时候我们整理文件的时候需要列出文件夹里面所有的文件名或者文件夹名,生成一个文件目录,一个个重命名然后复制到word或者记事本的方法显示有点太繁琐了。网上有一些自动生成文件目录的程序,比如我之前一直在用的DirIndex.exe。但最近我发现_bat获取文件夹下所有文件名和文件夹名称
文章浏览阅读914次,点赞18次,收藏18次。部署方式选择公有云部署,训练方式均可。增量训练的意思是在之前训练的模型基础上再次进行训练,如果事先没有进行过训练,这一项为不可选中状态。回到Postman,参数栏按如下方式填写,其中第一个KEY-VALUE值直接照写,client_id和client_secret的VALUE值分别为上一步获取的AK、SK。如果数据集质量够高,每种标签标注效果都很好,也可以在模型训练时再进行数据增强,或者直接跳过这一步。在导入界面配置导入信息,选择本地导入,导入压缩包(其他导入方式请自行测试),如图1.1.2。_easydl paddlex bml
文章浏览阅读1.4k次。Red Hat Enterprise Linux Version / UpdateRed Hat Enterprise Linux – Kernel version / redhat-release stringOracle Linux – Kernel version / release stringsRed Hat Enterprise Linux 7Red Hat Enterprise Li..._oracle linux redhat 对应关系
文章浏览阅读804次。2020年中南大学研究生招生夏令营机试题题目链接A题题目描述众所周知,彩虹有7种颜色,我们给定七个 字母和颜色 的映射,如下所示:‘A’ -> “red”‘B’ -> “orange”‘C’ -> “yellow”‘D’ -> “green”‘E’ -> “cyan”‘F’ -> “blue”‘G’ -> “purple”但是在某一..._中南大学 计算机 夏令营 笔试
文章浏览阅读2.9k次。一、介绍cmake提供了一组内置宏,用户可以自己设置。只有当该集合中的其他条件为真时,该宏才会向用户提供一个选项。语法include(CMakeDependentOption)CMAKE_DEPENDENT_OPTION(USE_FOO "Use Foo" ON "USE_BAR;NOT USE_ZOT" OFF)如果USE_BAR为true而USE_ZOT为false,则提供一个默认为ON的选项USE_FOO。否则,它将USE_FOO设._cmake_dependent_option