牛客day1
0、概述:技术架构:Spring BootSpring 、SpringMVC、MybatisRedis、Kafka、Elasticsearch(搜索引擎)Spring Security、Spring Actuator开发环境:1、技术准备1.搭建开发环境1.mavenApache Maven管理jar包maven仓库:存放构件的位置本地仓库:默认是~/.m2/repository远程仓库:中央仓库
0、概述:
技术架构:
Spring Boot
Spring 、SpringMVC、Mybatis
Redis、Kafka、Elasticsearch(搜索引擎)
Spring Security、Spring Actuator
开发环境:
1、技术准备
1.搭建开发环境
1.maven
Apache Maven
- 管理jar包
- maven仓库:存放构件的位置
- 本地仓库:默认是~/.m2/repository
- 远程仓库:中央仓库、镜像仓库、私服仓库
- 示例:安装、配置、常用命令
cd mavendemo1 带有pom.xml路径下
mvn compile 编译的结果在target中(test不会编译)
重新编译 mvn clean mvn compile
mvn clean compile
mvn clean test maven clean之后再测试一下(test包含compile)
2.Intellij IDEA
配置maven(查找jar包 mvnrepository 根据使用量)
快速开始archetype-quickstart
Build Project Ctrl +F9
3.Spring Initializr(解决包依赖问题)
-
创建Spring Boot项目的引导工具(底层基于maven)
2.1.5
-
创建“牛客社区"项目
4.Spring Boot入门示例
-
Spring Boot核心作用
- 起步依赖、自动配置、端点监控
-
示例
-
一个简单的处理客户端请求案例
-
controller.AlphaController:
-
@Controller @RequestMapping("/alpha") //方法前面的uri名字 即对应的Controller public class AlphaController{ @RequestMapping("/hello") @ResponseBody public String SayHello(){ return "hello Spring boot"; } }
-
-
修改端口的配置:
server.port=8080
server.servlet.context-path=/community
#项目访问路径
自动编译、自动重启:DevTools的作用
2.Spring入门
- Spring Framework
- Spring Boot
- Spring Cloud
- Spring Cloud Data Flow
1.Spring Framework
- Spring Core
- IOC、AOP(用来管理对象的一种思想)
- 由Spring管理的对象,我们通常叫做Bean,IOC基于面向对象的思想;AOP基于面向切面编程的思想(对面向对象的补充)(能够管理一切Bean、第三方的框架拿过来也能进行整合)
- Spring Data Access(Spring访问数据库的功能)
- Transactions、Spring MyBatis
- Web Servlet(Web开发)
- Spring MVC
- Integration(web集成)
- Email、Scheduling、AMQP、Security
(GA正式版本)
springIOC(基石)
- Inversion of Control
- 控制反转,是一种面向对象编程的设计思想
- Dependency Injection
- 依赖注入,是IOC思想的实现方式
- IOC Container
- IOC容器,是实现依赖注入的关键,本质上是一个工厂
降低Bean对象之间的耦合
给Spring容器两份数据:才可以自动实例化Bean供你调用
1、管理哪些Bean,Bean的类型
2、配置文件里的配置,通过配置描述这些Bean之间的关系
(关联关系体现在配置文件中)
解释Spring Boot入门案例:
springboot应用启动了,底层启动了tomcat;自动帮我们创建了Spring容器
@SpringBootApplication
public class CommunityApplication{
public static void main(String[] args){
SpringApplication.run(CommunityApplication.class,args);
}
}
在web项目当中,spring容器不需要我们自己手动创建,在
SpringApplication.run(CommunityApplication.class,args);
底层自动创建Spring容器。
Spring容器创建完,它会自动扫描某些包下的某些Bean,将这些Bean装配到容器中,那么哪些类会被扫描到呢?
Spring应用启动的时候需要配置的,注解@SpringBootApplication标识的类表明这是一个配置文件。启动了自动配置
@SpringBootApplication
启动了组件扫描,扫描条件:
1、配置类所在的包下所有的类,
2、标明了注解@Controller @Service @Component @Repository 都能被扫描到,业务@Service,控制层@Controller,数据库访问@Repository,任何地方都能用Component。
Springboot中的IOC容器:
如何得到IOC容器,实现ApplicationContextAware
IOC的顶层接口BeanFactory,一般多用ApplicationContext
测试应用上下文即IOC容器:
①test类中的测试:
test包里面对应的CommunityApplicationTests.java
@SpringBootTest //Springboot的测试
//测试时以主程序作为配置类
@ContextConfiguration(classes = CommunityApplication.class)
②实现获取容器的接口ApplicationContextAware
public class CommunityApplicationTests implements ApplicationContextAware {}
③通过传入ApplicationContext获取容器ApplicationContext
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
④证明容器得到Bean
1.测试应用上下文Spring容器 证明ApplicationContext的实质实现类是org.springframework.web.context.support.GenericWebApplicationContext@282cb7c7
GenericWebApplicationContext
2.先有一个dao层,其中对应的接口和不同的接口实现类 如AlphaDao接口和AlphaDaoMyBatisImpl;AlphaDaoHibernateImpl
通过getBean(Interface.class)的方法获取Bean,能够获取到实现了接口的方法,但只限一个;
如果有多个可在其标注@Primary注解,否则需要:
通过getBean(InterfaceImpl.class)的方法获取Bean
@Test
public void testApplicationContext() {
System.out.println(applicationContext);
//依赖的是接口,实现类变了,接口不用动,但是只会挑选一个,当重复加@Primary注解,实现只加载这一个
//降低耦合,使得实现类和接口不发生耦合
AlphaDao alphaDao = applicationContext.getBean(AlphaDao.class);
AlphaDao alphaDao1 = applicationContext.getBean(AlphaDaoMyBatisImpl.class);
AlphaDao alphaDao2 = applicationContext.getBean(AlphaDaoHibernateImpl.class);
System.out.println(alphaDao.select());
System.out.println(alphaDao1.select());
System.out.println(alphaDao2.select());
}
3.通过接口实现类的名字获取(字符串的方式),获取的时候是Object对象,要么强转,要么后面加上类型限定(即接口)
@Repository("alphaHibernate")
alphaDao3 = applicationContext.getBean("alphaHibernate",AlphaDao.class);
System.out.println(alphaDao3.select());
接口及其实现类:
public interface AlphaDao {
String select();
}
@Repository("alphaHibernate")
public class AlphaDaoHibernateImpl implements AlphaDao {
@Override
public String select() {
return "hibernate";
}
}
@Repository
@Primary
//加上primary会被优先的装配
public class AlphaDaoMyBatisImpl implements AlphaDao {
@Override
public String select() {
return "MyBatis";
}
}
⑤证明容器管理Bean(自动化创建,销毁)
@Test
public void testBeanManagement() {
AlphaService alphaService = applicationContext.getBean(AlphaService.class);
System.out.println(alphaService);
}
该程序启动时Bean被实例化,程序停止的时候被销毁
说明Bean只被实例化一次,只被销毁一次,说明是单例的
执行两次的Bean是同一个
@Test
public void testBeanManagement() {
AlphaService alphaService = applicationContext.getBean(AlphaService.class);
System.out.println(alphaService);
alphaService = applicationContext.getBean(AlphaService.class);
System.out.println(alphaService);
}
结果:
com.nowcoder.community.service.AlphaService@3659d7b1
com.nowcoder.community.service.AlphaService@3659d7b1
如果不想单例的,那么需要在Bean的注解上,添加@Scope注解,保证不是单例的(默认singleton)
@Scope("prototype")
改了之后,Bean的hashCode不一致
⑥测试配置Config
测试配置类:放在配置包config下
配置类
@Configuration
public class AlphaConfig {
@Bean
//这个方法返回的对象将会被装配到容器里
public SimpleDateFormat simpleDateFormat() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
}
@Bean注解表示返回的对象会被装配到容器里
测试:
@Test
public void testBeanConfig() {
SimpleDateFormat simpleDateFormat =
applicationContext.getBean(SimpleDateFormat.class);
System.out.println(simpleDateFormat.format(new Date()));
}
2021-01-02 20:13:48
这些都是主动获取
⑦验证自动注入
最好需要自动注入
@Autowired
private AlphaDao alphaDao;
@Autowired
private AlphaService alphaService;
@Autowired
private SimpleDateFormat simpleDateFormat;
@Test
public void testDi() {
System.out.println(alphaDao);
System.out.println(alphaService);
System.out.println(simpleDateFormat);
}
//需要注入其他的实现类,再加一个注解Qualifier(name)
@Autowired
@Qualifier("alphaHibernate")
private AlphaDao alphaDao;
com.nowcoder.community.dao.AlphaDaoHibernateImpl@25e49cb2
com.nowcoder.community.service.AlphaService@7f7af971
java.text.SimpleDateFormat@4f76f1a0
由controller来处理浏览器的请求,controller会调用业务组件service,业务组件service会调用dao访问数据库,
controller–>service–>dao
因此可以在service类中添加private 的dao接口类 并添加@Autowired注解
@Service
public class AlphaService {
@Autowired
private AlphaDao alphaDao;
public String find() {
return alphaDao.select();
}
}
同样的道理,controller层调用service
在controller类中添加private 的service类 并添加@Autowired注解,注意controller有效的前提是需要标注访问路径
//@RestController
@RequestMapping("/alpha")
//方法前面的uri名字
@Controller
public class AlphaController {
@Autowired
private AlphaService alphaService;
//controller的前提是需要标注访问路径
@RequestMapping("/data")
@ResponseBody
public String getData() {
return alphaService.find();
}
}
测试是否自动注入成功: 启动主程序:
测试的代码:
config包:
@Configuration
public class AlphaConfig {
@Bean
//这个方法返回的对象将会被装配到容器里
public SimpleDateFormat simpleDateFormat() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
}
controller:
//@RestController
@RequestMapping("/alpha")
//方法前面的uri名字
@Controller
public class AlphaController {
@Autowired
private AlphaService alphaService;
//controller的前提是需要标注访问路径
@RequestMapping("/data")
@ResponseBody
public String getData() {
return alphaService.find();
}
@RequestMapping("/hello")
@ResponseBody
public String hello() {
return "nowCoder";
}
}
dao包:
public interface AlphaDao {
String select();
}
@Repository("alphaHibernate")
public class AlphaDaoHibernateImpl implements AlphaDao {
@Override
public String select() {
return "hibernate";
}
}
@Repository
@Primary
//加上primary会被优先的装配
public class AlphaDaoMyBatisImpl implements AlphaDao {
@Override
public String select() {
return "MyBatis";
}
}
service包:
@Service
//@Scope("prototype")
//默认singleton 单例;prototype非单例的
public class AlphaService {
@Autowired
private AlphaDao alphaDao;
public AlphaService() {
System.out.println("实例化AlphaService");
}
@PostConstruct
//证明容器会自动管理Bean,比如初始化创建,销毁,要让容器自动调用,
// @PostConstruct注解表明方法在构造后调用
//初始化方法
public void init() {
System.out.println("初始化AlphaService");
}
@PreDestroy
public void destroy() {
System.out.println("销毁AlphaService");
}
public String find() {
return alphaDao.select();
}
}
主启动类:
@SpringBootApplication
public class CommunityApplication {
public static void main(String[] args) {
SpringApplication.run(CommunityApplication.class, args);
}
}
主测试类:
@SpringBootTest
//测试时以主程序作为配置类
@ContextConfiguration(classes = CommunityApplication.class)
public class CommunityApplicationTests implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Test
public void testApplicationContext() {
System.out.println(applicationContext);
//依赖的是接口,实现类变了,接口不用动,但是只会挑选一个,当重复加@Primary注解,实现只加载这一个
//降低耦合,使得实现类和接口不发生耦合
AlphaDao alphaDao = applicationContext.getBean(AlphaDao.class);
AlphaDao alphaDao1 = applicationContext.getBean(AlphaDaoMyBatisImpl.class);
AlphaDao alphaDao2 = applicationContext.getBean(AlphaDaoHibernateImpl.class);
AlphaDao alphaDao3 = applicationContext.getBean(AlphaDaoTestImpl.class);
System.out.println(alphaDao.select());
System.out.println(alphaDao1.select());
System.out.println(alphaDao2.select());
System.out.println(alphaDao3.select());
alphaDao3 = applicationContext.getBean("alphaHibernate", AlphaDao.class);
System.out.println(alphaDao3.select());
}
@Test
public void testBeanManagement() {
AlphaService alphaService = applicationContext.getBean(AlphaService.class);
System.out.println(alphaService);
alphaService = applicationContext.getBean(AlphaService.class);
System.out.println(alphaService);
}
@Test
public void testBeanConfig() {
SimpleDateFormat simpleDateFormat =
applicationContext.getBean(SimpleDateFormat.class);
System.out.println(simpleDateFormat.format(new Date()));
}
//需要注入其他的实现类,再加一个注解Qualifier(name)
@Autowired
@Qualifier("alphaHibernate")
private AlphaDao alphaDao;
@Autowired
private AlphaService alphaService;
@Autowired
private SimpleDateFormat simpleDateFormat;
@Test
public void testDi() {
System.out.println(alphaDao);
System.out.println(alphaService);
System.out.println(simpleDateFormat);
}
}
2.Spring MVC
学习文档网页:
https://developer.mozilla.org/zh-CN/
FastStone Draw
为了解耦更有利于维护
三层架构和MVC不是一个东西:
三层架构是指整个web分层:表现层;业务层;数据层;
MVC:
Model 模型层;(数据)
View:视图层;
Controller控制层
MVC主要解决表现层的问题,当浏览器访问服务器的时候,访问的是Controller组件,会接收请求中的数据,调用业务层处理,处理完后得到的数据封装到Model里面,传给视图层,视图层利用model数据生成一个HTML返回给浏览器。(View渲染)
核心组件:
前端控制器:DispatcherServlet
模板引擎:(需要的材料:模板文件+Model)生成HTML
https://www.thymeleaf.org/
启动关闭缓存,降低服务器压力
spring.io的appendix有thymleaf
获取请求
@RequestMapping("/http")
public void http(HttpServletRequest request, HttpServletResponse response) {
//获取请求数据
System.out.println(request.getMethod());
System.out.println(request.getServletPath());
//请求行所有数据
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();//key
String value = request.getHeader(name);//value
System.out.println(name + ":" + value);
}
//传入参数code
System.out.println(request.getParameter("code"));
//返回响应数据,网页版本
response.setContentType("text/html;charset=utf-8");
//输出流
try (
PrintWriter writer = response.getWriter();//java7的默认会关闭,前提有close方法
) {
writer.write("<h1>牛客网 </h1>");
} catch (IOException e) {
e.printStackTrace();
}
}
Get请求1 路径方法中传入参数(默认)
形参中的:
@RequestParam(name,required,defaultValue)
注解:
/students?current=1&limit=20 加参数 1-10 3-20
@RequestMapping(path = "/students", method = RequestMethod.GET)
路径是/students
下 ,method为get方法
形参:(先准备传入,再设置)
get请求获取参数怎么获取?
在方法形参中传入表示要取的这个参数current、limit,DispatcherServlet 检测到current和limit之后,会把
@RequestParam(name,required,defaultValue)一个一个对应赋值;请求参数中的name = "current"意思是request当中名为current的值给current,required=false,意思是也可以不传入这个参数,defaultValue 不传的时候默认值
public String getStudents(
@RequestParam(name = "current", required = false, defaultValue = "1") int current,
@RequestParam(name = "limit", required = false, defaultValue = "10") int limit) {
代码:
// /students?current=1&limit=20 加参数 1-10 3-20
@RequestMapping(path = "/students", method = RequestMethod.GET)
@ResponseBody
public String getStudents(
@RequestParam(name = "current", required = false, defaultValue = "1") int current,
@RequestParam(name = "limit", required = false, defaultValue = "10") int limit) {
System.out.println(current);
System.out.println(limit);
return "some students";
}
Get请求2 参数是路径的一部分
{parameter} &@PathVariable
例如:/student/123
get请求表示从服务器获取数据,post请求表示从浏览器向服务器提交数据
直接把参数编排到路径当中,当参数成为路径中的一部分就不是上面那样获取
注解:
@RequestMapping(path = "/student/{id}", method = RequestMethod.GET)
/path:"/student/{id}",id表示变量 变量化后可从中取值
形参:
public String getStudent(@PathVariable("id") int id) {
@PathVariable
表示路径变量,括号里写上变量的名字,注解会从路径中的得到变量赋给形参id(保持一致)
// /student/123
//path:"/student/{id}",id表示变量
@RequestMapping(path = "/student/{id}", method = RequestMethod.GET)
@ResponseBody
public String getStudent(@PathVariable("id") int id) {
System.out.println(id);
return "a student";
}
Post请求
通常用post请求,从浏览器向服务器提交数据,后台应该怎么获取呢?浏览器向服务器提交数据,浏览器首先应该具有一个表单的网页
get请求也能传参,但是参数在明面上,且输入框有限,
resources/static/html/student.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>增加学生</title>
</head>
<body>
<form method="post" action="/community/alpha/student">
<!--表单需要文本框用来输入数据-->
<p>
姓名:<input type="text" name="name">
</p>
<p>
年龄:<input type="text" name="age">
</p>
<p>
<input type="submit" value="保存">
</p>
</form>
</body>
</html>
静态资源访问路径:community/html/student.html
http://localhost:8080/community/html/student.html
填写完表单之后,点击保存提交转到DispatchServlet控制分发后的controller下:
community/alpha/student
http://localhost:8080/community/alpha/student
controller的方法:
@RequestMapping 照样表示访问路径,method为RequestMethod.Post 表示提交
只需要直接声明参数,声明的参数与form表单的参数一致就会自动传入进来。(这里传入之后打印在控制台)
注解:@RequestMapping(path = "/student", method = RequestMethod.POST)
形参:public String saveStudent(String name, int age) {
在方法列表形参中写入与form表单一样的值即可
@RequestMapping(path = "/student", method = RequestMethod.POST)
@ResponseBody
public String saveStudent(String name, int age) {
System.out.println(name);
System.out.println(age);
//只要直接声明参数,声明的参数与form表单的参数一致就会自动传入进来
return "success";
}
返回网页1
响应HTML数据,DispatcherServlet会调用某个controller方法,controller方法会返回ModelAndView数据,ModelAndView数据传给模板引擎,由模板引擎进行渲染,生成动态的HTML.
//需要new ModelAndView
mav.setViewName("/demo/view");
return的是templates下demo包里view.html
@RequestMapping(path = "/teacher", method = RequestMethod.GET)
public ModelAndView getTeacher() {
ModelAndView mav = new ModelAndView();
mav.addObject("name", "张三");
mav.addObject("age", 30);
mav.setViewName("/demo/view");//指的是templates下demo包里view.html
return mav;
}
返回view所指向的html 路径:templates/demo/view.html
第一个段落写入姓名,第二个写入年龄,这些是动态的值,需要借助模板引擎解决:
th即tymeleaf的缩写,:p的文本表示这里的一个变量,变量用el表达式来写,name是从ModelAndView中取的值
模板引擎渲染的时候,会获取name变量放进p里面
<!DOCTYPE html>
<!---->
<!--需要声明这是一个模板,而不只是一个网页,只需要声明其来自模板网址就行-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Teacher</title>
</head>
<body>
<p th:text="${name}"></p>
<p th:text="${age}"></p>
</body>
</html>
返回网页2
形参:public String getSchool(Model model)
return的还是路径,String表示view的返回路径,model数据怎么传呢,因为返回的view,不可能带model,model放进方法体的形参列表中,Model不是我们创建的,
是DispatchServlet在调用方法时,会自动实例化Model对象,这是一个Bean,DispatchServlet持有该对象的引用,可以往这里面写入数据,它也能得到,
区别:
与前者不同,前者是把Model和View都装进ModelAndView,后者是把Model装进参数里,视图View直接返回。返回的值给了DispatcherServlet,而Model的引用也被DispatcherServlet持有,所以这两个数都能得到。
相比第二种方法更简易,不用手动new ModelAndView对象
//与前者类似,
@RequestMapping(path = "/school", method = RequestMethod.GET)
public String getSchool(Model model) {
model.addAttribute("name", "北京大学");
model.addAttribute("age", 80);
return "demo/view";
}
响应Json数据
响应JSON数据(异步请求)
java对象–>JSON字符串 -->(浏览器利用JSON数据封装成) JS对象
任何语言有字符串类型,任何语言都能解析,方便跨语言 异步请求(成功、失败响应结果)
异步:不刷新页面能够访问服务器得到一次非html的响应
将json字符串封装成Map返回
@RequestMapping(path = "/emp", method = RequestMethod.GET)
@ResponseBody
public Map<String, Object> getEmp() {
Map<String, Object> emp = new HashMap<>();
emp.put("name", "张三");
emp.put("age", 23);
emp.put("salary", 8000.00);
return emp;
}
属性和属性值构成
响应Json数据2
多个json对象的Map格式封装进List中
@RequestMapping(path = "/emps", method = RequestMethod.GET)
@ResponseBody
public List<Map<String, Object>> getEmps() {
List<Map<String, Object>> list = new ArrayList<>();
Map<String, Object> emp = new HashMap<>();
emp.put("name", "张三");
emp.put("age", 23);
emp.put("salary", 8000.00);
list.add(emp);
emp = new HashMap<>();
emp.put("name", "李四");
emp.put("age", 24);
emp.put("salary", 7000.00);
list.add(emp);
emp = new HashMap<>();
emp.put("name", "王五");
emp.put("age", 25);
emp.put("salary", 9000.00);
list.add(emp);
return list;
}
结果:
3.Mybatis
1.首先安装Mysql
Mysql Server和Mysql Workbench
先解压 配置my.ini
配置bin的环境变量
mysqld --initialize --console
mysqld install
目前自动配的文件在
D:\work\mysql-8.0.22-winx64\data 中
net start mysql 发生系统错误2 系统找不到指定的文件
以管理员身份运行:
mysqld --remove
mysqld --install
修改密码:
ALTER USER root@localhost IDENTIFIED by '123456'
导入sql命令Source
source 路径 左斜线
2.MyBatis
https://mybatis.org/mybatis-3/
http://mybatis.org/spring/
i-DDL 可查看建表语句
核心组件:
-SqlSessionFactory:用于创建SqlSession的工厂类
-SqlSession:MyBatis的核心组件,用于向数据库执行SQL,相当于JDBC中的Connection
-主配置文件:XML配置文件,可以对MyBatis的底层行为连接参数,连接池的配置
Tips:在springboot中,前三个自动初始化了(xml配置可以配置进properties中)
-Mapper接口:就是DAO接口,在Mybatis中习惯的称为Mapper
-Mapper映射器:用于编写SQL,并将SQL和实体类映射的组件,采用XML、注解均可实现
示例1:
entity/User.java
–>dao/UserMapper.Interface
–>resources/mapper/user-mapper.xml
1.先需要一个与table对应的实体类,在entity包中含有get、set、toString方法;
2.再写UserMapper,添加@Mapper注解,其中的方法即实现对数据的操作:比如selectById(int id);insertUser(User user)
mybatis的好处就在于不需要写接口实现类,只需要接口,其中利用配置自动底层实现生产实现类。
3.再写UserMapper的配置文件
user-mapper.xml 放在mapper路径下
1:entity/User
public class User {
private int id;
private String username;
private String password;
private String salt;
private String email;
private int type;
private int status;
private String activationCode;
private String headerUrl;
private Date createTime;
...
}
2:dao/UserMapper
//@Mapper 和下面组件一样交给IOC管理
@Repository 加这个不会显示warning
//@Mapper
@Repository
public interface UserMapper {
User selectById(int id);
User selectByName(String username);
User selectByEmail(String email);
int insertUser(User user);
int updateStatus(int id, int status);
int updateHeader(int id, String headerUrl);
int updatePassword(int id, String password);
}
3:user-mapper.xml 配置实体类和UserMapper的
head部分:复制即可:统一形式
<?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">
namespace=""
表示是为哪个mapper服务的,需要标注全限定类名,指定到mapper–>
<mapper namespace="com.nowcoder.community.dao.UserMapper">
</mapper>
测试select
1.selectById:
<select id="selectById" resultType="User" >
select <include refid="selectFields"></include>
from user
where id=#{id}
</select>
2.selectByName:
<select id="selectByName" resultType="User">
select <include refid="selectFields"></include>
from user
where username=#{username}
</select>
3.selectByEmail
<select id="selectByEmail" resultType="User">
select id,username,password,salt,email,
type,status,activation_code,header_url,create_time
from user
where email=#{email}
</select>
当反复涉及到字段名称,可以用sql管理
sql id是必须要的 后面用<include refid="selectFields">
指定特定的语句字段
<sql id="selectFields">
id,username,password,salt,email,type,status,activation_code,header_url,create_time
</sql>
对应的测试类:
//@ComponentScan(basePackages =“com.nowcoder.community.dao”) 添加防止组件扫描不到,也可以放在配置类中
测试的时候,要注意会用到UserMapper的接口的方法,故需在测试类中自动注入UserMapper
@SpringBootTest
//测试时以主程序作为配置类
@ContextConfiguration(classes = CommunityApplication.class)
public class MapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void testSelectUser() {
User user = userMapper.selectById(101);
System.out.println(user);
User user1 = userMapper.selectByName("liubei");
System.out.println(user1);
User user2 = userMapper.selectByEmail("nowcoder101@sina.com");
System.out.println(user2);
}
同理:
测试Insert
#{username} 等等确保和User类对应 是驼峰法 从中取值再插入进mysql的数据库中;
而insertFields是对应数据库的_小写写法
<insert id="insertUser" parameterType="User" keyProperty="id">
insert into user(<include refid="insertFields"></include>)
values(#{username},#{password},#{salt},#{email},
#{type},#{status},#{activationCode},#{headerUrl},#{createTime})
</insert>
<sql id="insertFields"> username,password,salt,email,type,status,activation_code,header_url,create_time
</sql>
测试类
int rows = userMapper.insertUser(user);
返回的是改变的行数;
@Test
public void testInsertUser() {
User user = new User();
user.setUsername("test");
user.setPassword("123456");
user.setSalt("abc");
user.setEmail("test@qq.com");
user.setHeaderUri("http://www.nowcoder.com/101.png");
user.setCreateTime(new Date());
int rows = userMapper.insertUser(user);
System.out.println(rows);
System.out.println(user.getId());
}
测试Update
<update id="updateStatus">
update user set status=#{status} where id=#{id}
</update>
<update id="updatePassword">
update user set password=#{password} where id=#{id}
</update>
<update id="updateHeader">
update user set header_url=#{headerUrl} where id=#{id}
</update>
测试类:
@Test
public void updateUser() {
int rows = userMapper.updateStatus(150, 1);
System.out.println(rows);
rows = userMapper.updateHeader(150, "http://www.nowcoder.com/102.png");
System.out.println(rows);
rows = userMapper.updatePassword(150, "1234567");
System.out.println(rows);
}
总的user-mapper.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服务的,需要标注全限定类名-->
<mapper namespace="com.nowcoder.community.dao.UserMapper">
<sql id="insertFields">
username,password,salt,email,type,status,activation_code,header_url,create_time
</sql>
<sql id="selectFields">
id,username,password,salt,email,type,status,activation_code,header_url,create_time
</sql>
<select id="selectById" resultType="User" >
select <include refid="selectFields"></include>
from user
where id=#{id}
</select>
<select id="selectByName" resultType="User">
select <include refid="selectFields"></include>
from user
where username=#{username}
</select>
<select id="selectByEmail" resultType="User">
select id,username,password,salt,email,
type,status,activation_code,header_url,create_time
from user
where email=#{email}
</select>
<insert id="insertUser" parameterType="User" keyProperty="id">
insert into user(<include refid="insertFields"></include>)
values(#{username},#{password},#{salt},#{email},
#{type},#{status},#{activationCode},#{headerUrl},#{createTime})
</insert>
<update id="updateStatus">
update user set status=#{status} where id=#{id}
</update>
<update id="updatePassword">
update user set password=#{password} where id=#{id}
</update>
<update id="updateHeader">
update user set header_url=#{headerUrl} where id=#{id}
</update>
</mapper>
4.开发社区首页
设计过程:
entity/DiscussPost.java
–>dao/UserMapper.Interface
–>resources/mapper/user-mapper.xml
1.entity:
public class DiscussPost {
private int id;
private int userId;
private String title;
private String content;
private int type;
private int status;
private Date createTime;
private int commentCount;
private double score;
...
}
2.dao
//@Param用于给参数取别名,
//如果只有一个参数,并且在里使用,则必须加别名
@Repository
public interface DiscussPostMapper {
List<DiscussPost> selectDiscussPosts(int userId, int offset, int limit);
//@Param用于给参数取别名,
//如果只有一个参数,并且在<if>里使用,则必须加别名
int selectDiscussPostRows(@Param("userId") int userId);
}
3.mapper:
<?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服务的,需要标注全限定类名-->
<mapper namespace="com.nowcoder.community.dao.DiscussPostMapper">
</mapper>
select语句对应的sql:复杂查询;
包含 正常显示(status!=2)
条件判断是否传入id <if test=“userId!=” /if>
id不为0需要拼接userId
排序:order by type 类型 降序 保证置顶
creat_time 降序 保证最新在前面
筛选,分页 offset 和limit
<sql id="selectFields">
id,user_id,title,content,type,status,create_time,comment_count,score
</sql>
<select id="selectDiscussPosts" resultType="DiscussPost">
select <include refid="selectFields"></include>
from discuss_post
where status!=2
<if test="userId!=0">
and user_id=#{userId}
</if>
order by type desc,create_time desc
limit #{offset},#{limit}
</select>
查询总的帖子数量
<select id="selectDiscussPostRows" resultType="int">
select count(id)
from discuss_post
where status!=2
<if test="userId!=0">
and user_id=#{userId}
</if>
</select>
4.service
一定不要跨层调用 必须service调dao,在查询过程中会封装成DiscussPost,但是会显示userId,不需要显示userId
两种做法:
1.关联查询,在查询该表的时候同时查询用户表
2.单独的查一下user再与DiscussPost合并一起封装,在后面与redis缓存数据的时候方便,性能更高。这里选择第二种
DiscussPostService:
@Service
public class DiscussPostService {
@Autowired
private DiscussPostMapper discussPostMapper;
public List<DiscussPost> findDiscussPosts(int userId, int offset, int limit) {
return discussPostMapper.selectDiscussPosts(userId, offset, limit);
}
public int findDiscussPostRows(int userId) {
return discussPostMapper.selectDiscussPostRows(userId);
}
}
UserService:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User findUserById(int id) {
return userMapper.selectById(id);
}
}
5.controller
在controller层中调service,包含DiscussPostService
和
UserService
①路径:@RequestMapping(path = "/index", method = RequestMethod.GET)
②方法:Model和View分开:
public String getIndexPage(Model model) {
model.addAttribute("discussPosts", discussPosts);
return "/index";
}
③从discussPostService中获取到的帖子封装进List中;
List<DiscussPost> list = discussPostService.findDiscussPosts(0, 0, 10);
④由于需要重新组装,新建一个集合List存放Map
List<Map<String, Object>> discussPosts = new ArrayList<>();
⑤如果list不为空,循环遍历post
if (list != null) {
for (DiscussPost post : list) {
}
⑥将post放入map中;
Map<String, Object> map = new HashMap<>();
map.put("post", post);
⑦由UserService的findUserById获取user用户,放入map中,并将map保存在discussPosts这个list中
User user = userService.findUserById(post.getUserId());
map.put("user", user);
discussPosts.add(map);
Controller总代码:
@Controller
public class HomeController {
@Autowired
private DiscussPostService discussPostService;
@Autowired
private UserService userService;
@RequestMapping(path = "/index", method = RequestMethod.GET)
public String getIndexPage(Model model) {
List<DiscussPost> list = discussPostService.findDiscussPosts(0, 0, 10);
//需要重新组装,新建一个集合
List<Map<String, Object>> discussPosts = new ArrayList<>();
if (list != null) {
for (DiscussPost post : list) {
Map<String, Object> map = new HashMap<>();
map.put("post", post);
User user = userService.findUserById(post.getUserId());
map.put("user", user);
discussPosts.add(map);
}
}
model.addAttribute("discussPosts", discussPosts);
return "/index";
}
}
6.html
由于controller返回路径是index
利用thymeleaf模板引擎重新构建动态的html `th
(1)修改的地方:
①引入模板引擎
xmlns:th="http://www.thymeleaf.org"
②将相对路径的css,js修改
th:"@{}"
由
<link rel="stylesheet" href="css/global.css" />
<script src="/js/global.js"></script>
<script src="/js/index.js"></script>
修改:
<link rel="stylesheet" th:href="@{/css/global.css}" />
<script th:src="@{/js/global.js}"></script>
<script th:src="@{/js/index.js}"></script>
③帖子详情的各个参数
- 去掉重复的ul 利用foreach循环输出: 每次遍历得到map数据
<th:each="map:${discussPosts}">
在ul后面添加: th:each="${}" 括号里面的即由Model返回的discussPostsJson数据,命名为map进行后面使用。
- 用户头像:
th:src="${}"
map.user.headerUrl 相当于
map.getUser().getHeadUrl()
由于是json数据即.底层是get
<img src="http://images.nowcoder.com/head/1t.png">
修改为:
<img th:src="${map.user.headerUrl}">
- 用户发帖标题内容
<a href="site/discuss-detail.html">
修改为:
<th:utext="${map.post.title}">
- 置顶和加精
<span class="badge badge-secondary bg-primary">置顶</span>
<span class="badge badge-secondary bg-danger">精华</span>
修改为:
<th:if="${map.post.type==1}">
<th:if="${map.post.status==1}">
- 用户发帖名称
<a href="site/discuss-detail.html">
内容用#占位,th:utext="${}"
<a href="#" th:utext="${map.user.username}">
- 用户发帖时间
thymeleaf有格式化语法:
#dates.format() 第一个为取的参数,第二个是时间格式
<b th:text="${#dates.format(map.post.createTime,'yyyy--MM--dd HH:mm:ss')}">2019-04-15 15:32:18</b>
即
<u class="mr-3">寒江雪</u> 发布于 <b>2019-04-15 15:32:18</b>
改为:
<u class="mr-3" th:utext="${map.user.username}">寒江雪</u> 发布于 <b th:text="${#dates.format(map.post.createTime,'yyyy--MM--dd HH:mm:ss')}">2019-04-15 15:32:18</b>
(2)有bug调了好久的地方
1.首先 网页加载很慢,怀疑是js,css的样式出问题
通过network分析确实未加载出来,更换在线链接src
改完下面回来之后发现也能显示
2.帖子列表内容不出来
通过重点排查html中的帖子详情代码;
发现1;头像未改为thymeleaf格式;
2.createTime少些一个a
3.controller层map名字写错dicussPosts 少写一个s
head部分:
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">
尾部部分:
<script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" crossorigin="anonymous"></script>
中途修改的src:
<script src="https://cdn.staticfile.org/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.3.1/css/bootstrap.min.css">
<script src="https://cdn.staticfile.org/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/4.1.0/js/bootstrap.min.js"></script>
7.从page开始加强
优化html从entity开始添加page对象进行有效控制来回的jason数据
package com.nowcoder.community.entity;
//封装分页相关的信息
public class Page {
// 当前页码
private int current = 1;
// 显示上限
private int limit = 10;
// 数据总数(用于计算总页数)
private int rows;
// 查询路径(用于复用分页链接)
private String path;
public int getCurrent() {
return current;
}
public void setCurrent(int current) {
if (current >= 1) {
this.current = current;
}
}
public int getLimit() {
return limit;
}
public void setLimit(int limit) {
if (limit >= 1 && limit <= 100) {
this.limit = limit;
}
}
public int getRows() {
return rows;
}
public void setRows(int rows) {
if (rows >= 0) {
this.rows = rows;
}
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
//获取当前页的起始行
public int getOffset() {
// current * limit - limit
return (current - 1) * limit;
}
//获取总页数
public int getTotal() {
// rows / limit [+1]
if (rows % limit == 0) {
return rows / limit;
} else {
return rows / limit + 1;
}
}
//获取起始页码
public int getFrom() {
int from = current - 2;
return from < 1 ? 1 : from;
}
//获取结束页码
public int getTo() {
int to = current + 2;
int total = getTotal();
return to > total ? total : to;
}
}
将page也放进controller的处理 保证可以翻页
@Controller
public class HomeController {
@Autowired
private DiscussPostService discussPostService;
@Autowired
private UserService userService;
@RequestMapping(path = "/index", method = RequestMethod.GET)
public String getIndexPage(Model model, Page page) {
// 方法调用前,SpringMVC会自动实例化Model和Page,并将Page注入Model.
// 所以,在thymeleaf中可以直接访问Page对象中的数据.
page.setRows(discussPostService.findDiscussPostRows(0));
page.setPath("/index");
List<DiscussPost> list = discussPostService.findDiscussPosts(0, page.getOffset(), page.getLimit());
List<Map<String, Object>> discussPosts = new ArrayList<>();
if (list != null) {
for (DiscussPost post : list) {
Map<String, Object> map = new HashMap<>();
map.put("post", post);
User user = userService.findUserById(post.getUserId());
map.put("user", user);
discussPosts.add(map);
}
}
model.addAttribute("discussPosts", discussPosts);
return "/index";
}
}
<!-- 分页 -->
<nav class="mt-5" th:if="${page.rows>0}">
<ul class="pagination justify-content-center">
<li class="page-item">
<a class="page-link" th:href="@{${page.path}(current=1)}">首页</a>
</li>
<li th:class="|page-item ${page.current==1?'disabled':''}|">
<a class="page-link" th:href="@{${page.path}(current=${page.current-1})}">
上一页
</a>
</li>
<li th:class="|page-item ${i==page.current?'active':''}|"
th:each="i:${#numbers.sequence(page.from,page.to)}">
<a class="page-link" href="#" th:text="${i}">1
</a>
</li>
<li th:class="|page-item ${page.current==page.total?'disabled':''}|">
<a class="page-link" th:href="@{${page.path}(current=${page.current+1})}">
下一页
</a>
</li>
<li class="page-item">
<a class="page-link" th:href="@{${page.path}(current=${page.total})}">
末页
</a>
</li>
</ul>
</nav>
响应状态码的含义
查询手册:https://developer.mozilla.org/zh-CN/
200成功
302
302给一个建议,通过客户端调用,进行重定向,非常低耦合的方式进行页面调转
404 路径写错了
500 服务端接收到了请求,但是服务器出现了问题。
应该检查服务端的程序
服务端断点调试技巧
F8 下一行
F7 方法内部
F9 改变断点,程序自上而下执行,直到下一个断点为止
管理断点
分别查看以及设置是否可用
客户端断点调试技巧
下一行 F10
进入 F11
执行到底 F8
设置日志级别,并将日志输出到不同的终端
springboot默认启动日志:logback
http://logback.qos.ch/
http://logback.qos.ch/manual/index.html
跟踪级别,调试级别,普通级别,警告级别,错误级别
public interface Logger {
// Printing methods:
public void trace(String message);
public void debug(String message);
public void info(String message);
public void warn(String message);
public void error(String message);
}
比选中的更高的
配置类:
#logger 日志级别,该包下面的设置信息为debug
logging.level.com.nowcoder.community=debug
写一个测试类
@SpringBootTest
//测试时以主程序作为配置类
@ContextConfiguration(classes = CommunityApplication.class)
public class LoggerTests {
//slf4j
private static final Logger logger = LoggerFactory.getLogger(LoggerTests.class);
@Test
public void testLogger() {
System.out.println(logger.getName());
logger.debug("debug logger");
logger.info("info logger");
logger.warn("warn logger");
logger.error("error logger");
}
}
改变配置为warn
将test包注意引为:否则失效
import org.junit.jupiter.api.Test;
启动线程池:info
出现error trycatch error级别
开发过程中调试过程用,后面不需要用 debug
把日志输出打印到文件中
logging.file.name=D:/SpringbootPro/nowcoder/data/community.log
日志按照不同级别打印:
5mB以后再重复新开
需要log对应的xml文件:(第三章)
logback-spring.xml 必须放在resources根目录下
只需要改变包名,路径,内存大小等
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<contextName>community</contextName>
<property name="LOG_PATH" value="D:/SpringbootPro/nowcoder/data/"/>
<!-- 存放目录-->
<property name="APPDIR" value="community"/>
<!-- error file -->
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${APPDIR}/log_error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${APPDIR}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>5MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30</maxHistory>
</rollingPolicy>
<append>true</append>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>error</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- warn file -->
<appender name="FILE_WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${APPDIR}/log_warn.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${APPDIR}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>5MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30</maxHistory>
</rollingPolicy>
<append>true</append>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>warn</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- info file -->
<appender name="FILE_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${APPDIR}/log_info.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${APPDIR}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>5MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30</maxHistory>
</rollingPolicy>
<append>true</append>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>info</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- console -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>debug</level>
</filter>
</appender>
<logger name="com.nowcoder.community" level="debug"/>
<root level="info">
<appender-ref ref="FILE_ERROR"/>
<appender-ref ref="FILE_WARN"/>
<appender-ref ref="FILE_INFO"/>
<appender-ref ref="STDOUT"/>
</root>
</configuration>
5.版本控制
1.对代码进行备份
2.可以把代码每一次变更都记录下来
3.团队成员互相协作
代码上传备份
- 认识Git
中央服务器的形式管理代码,所有人把代码存放在一个版本控制的中央服务器,其有1,2,3不同版本的代码。
AB都需要从中下载修改再上传
但是AB每个主机上也有一个仓库
服务器 远程仓库;本地 本地仓库
代码是先存放到本地仓库,觉得没有问题了再上传推送到远程仓库。
都有 谁新用谁的
集中化的版本控制系统—>分布式
Git Bash 模拟Linux环境命令行
Git CMD Windows命令行
Git GUI 界面 很少
查看
git version
查看版本 我的是2.28.0
- Git常用命令
git config --list
git config --global user.name "rexocean"
git config --global user.email "15531124244@163.com"
上传本地仓库:
要让git管理 需要cd进到目录下(文件名下面就是src等等)
需要初始化
git init
这个命令会在该路径下产生.git 文件
查看git当前的的状态
git status
还没有添加到库里面
添加git add *
再次查看 全是绿的
只是临时存,没有提交,
提交 m写备注
git commit -m `Test1`
在用git status检查
修改已经提交的文件中代码
改完以后再查看状态 git status
显示是已修改的 是红色的 还未提交
继续提交仓库中 git add *
并提交
要传到远程仓库:
传递过程中要有ssl安全连接 需要创建一个密钥
牛客网测试:
新建项目 保证提交有位置
访问路径:
https://git.nowcoder.com/473369408/community1.git
声明 取别名origin
git remote add origin https://git.nowcoder.com/473369408/community1.git
git push -u origin master
输入密码就是绑定的邮箱和密码
从远程下载下来看
在项目上点克隆或下载 复制http
https://git.nowcoder.com/473369408/community1.git
打开要存储的位置
git clone https://git.nowcoder.com/473369408/community1.git
下载完成
测试github下的密钥
项目git地址:
git
https://blog.csdn.net/erlian1992/article/details/77200588ssh
https://blog.csdn.net/erlian1992/article/details/77200700
git imooc课程:
仓库:管理项目的中心,每个项目看成一个仓库
更多推荐
所有评论(0)