初识Spring Boot
使用Spring Initializr创建 Spring Boot 项目的引导工具。
通过该过程可以创建一个Web项目的模板,包含各种包,减少后期依赖配置等的操作量。
搜索加入:Jpa、Web、Thymeleaf、DevTools。项目中内嵌了一个Tomcat,在application.properties可以对端口和访问路径进行修改。
// 程序端口/项目访问路径
server.port=8080
server.servlet.context-path=/community
设置网站:start.spring.io
示例:创建“社区”项目。
controller 层创建AlphaController类,创建能够处理浏览器请求的方法。分别给类名和方法名加上访问路径。
// 代码实现
package com.nowcoder.community.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/alpha") // "/alpha":用于给下面的类AlphaController取一个访问的路径名。给浏览器提供一个服务,浏览器能够通过这个名字访问某个类中的某个方法。
public class AlphaController {
// 类中给浏览器提供一个访问服务的方法/能够处理浏览器请求的方法
@RequestMapping("/hello") // 方法也需要访问名
@ResponseBody
public String sayHello() {
return "Hello Spring Boot.";
}
}
打开浏览器访问:http://localhost:8080/community/alpha/hello,显示结果:Hello Spring Boot.
此时项目目录:
Spring容器:IoC、DI
获取Bean的方式:
1.通过指定接口获取Bean: applicationContext.getBean(AlphaDao.class);
2.通过指定Bean的名字及对应的接口获取Bean: applicationContext.getBean("alphaHiberate", AlphaDao.class);
3.通过自定义第三方配置类获取Bean: applicationContext.getBean(SimpleDateFormat.class);
4.通过DI(依赖注入)的方式获取Bean: 以注解的方式。如:
@Autowired
private AlphaDao alphaDao; // Spring 容器可以把 AlphaDao 注入给 alphaDao 这个属性
package com.nowcoder.community;
import com.nowcoder.community.config.AlphaConfig;
import com.nowcoder.community.dao.AlphaDao;
import com.nowcoder.community.service.AlphaService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import java.text.SimpleDateFormat;
import java.util.Date;
//@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class) // 标识测试代码会以CommunityApplication为配置类
public class CommunityApplicationTests implements ApplicationContextAware { // 哪个类想得到容器就实现该接口
private ApplicationContext applicationContext; // Spring 容器
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
/**
*
* ApplicationContext:这个参数就是 Spring 容器
* ApplicationContext 继承了 HierarchicalBeanFactory 继承了 BeanFactory(Spring 容器的顶层接口)
* 又由于ApplicationContext是子接口,拥有的功能更全面,所以通常使用ApplicationContext
*
*/
this.applicationContext = applicationContext;
}
@Test
public void testApplicationContext() {
System.out.println(applicationContext); // 证明容器是存在的,可用于管理Bean
// 从容器中获取Bean的方式 1
AlphaDao alphaDao = applicationContext.getBean(AlphaDao.class); // 面向接口编程思想
System.out.println(alphaDao.select());
// ... ...方式 2
alphaDao = applicationContext.getBean("alphaHiberate", AlphaDao.class); // 优先级较次的可以通过指定名字进行获取
System.out.println(alphaDao.select());
}
@Test
public void testBeanManagement() { // 测试Bean的管理方式,其实是管理Bean的作用范围
AlphaService alphaService = applicationContext.getBean(AlphaService.class);
System.out.println(alphaService);
alphaService = applicationContext.getBean(AlphaService.class);
System.out.println(alphaService); // 结果:com.nowcoder.community.service.AlphaService@6de43bc1。表示被Spring容器管理的Bean默认是单例的
}
/**
* 通过配置类自定义第三方Bean,方便自己的使用
* simpleDateFormat方法返回的对象会装配到容器里
* 其中Bean的名字就是:simpleDateFormat
*/
@Test
public void testBeanConfig() {
SimpleDateFormat simpleDateFormat =
applicationContext.getBean(SimpleDateFormat.class);
System.out.println(simpleDateFormat.format(new Date()));
}
/**
* 怎么用更方便?什么叫依赖注入?
* 方式:声明属性、写上@Autowired注解即可
*
*/
@Autowired
private AlphaDao alphaDao; // Spring 容器可以把 AlphaDao 注入给 alphaDao 这个属性
@Autowired
@Qualifier("alphaHiberate")
private AlphaDao alphaDaoHiberate; // 当前Bean依赖的是接口,和类是不耦合的,降低了耦合度
@Autowired
private AlphaService alphaService;
@Autowired
private AlphaConfig alphaConfig;
@Autowired
private SimpleDateFormat simpleDateFormat;
@Test
public void testDI() {
System.out.println(alphaDao);
System.out.println(alphaService);
System.out.println(alphaConfig);
System.out.println(simpleDateFormat);
System.out.println(alphaDaoHiberate);
}
}
模拟浏览器请求:以依赖注入的方式实现,采用SpringMVC的分层架构。controller层调用service,service层调用dao.
测试流程:
1.dao层
dao 层写接口Alpha,该接口有两种实现类,分别是 AlphaDaoHiberateImpl 和 AlphaDaoMyBatisImpl 。
该部分代码用于测试加上@Primary注解的实现类优先级更高,当需要获取指定类AlphaDaoHiberateImpl的Bean时,可以通过添加注解@Repository("alphaHiberate")指定Bean的名字,便于获取。
Interface:AlphaDao
package com.nowcoder.community.dao;
public interface AlphaDao {
String select();
}
AlphaDaoHiberateImpl继承接口,重写方法,指定Bean的名字
package com.nowcoder.community.dao;
import org.springframework.stereotype.Repository;
@Repository("alphaHiberate") // 通过("")可以自己指定Bean的名字
public class AlphaDaoHiberateImpl implements AlphaDao {
@Override
public String select() {
return "Hiberate";
}
}
AlphaDaoMyBatisImpl继承接口,重写方法,@Primary 注解表示优先级更高
package com.nowcoder.community.dao;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Repository;
@Repository
@Primary // 两个实现类继承同一个接口,加上该注解标识该类具有更高的优先级
public class AlphaDaoMyBatisImpl implements AlphaDao {
@Override
public String select() {
return "MyBatis";
}
}
2.service层
package com.nowcoder.community.service;
import com.nowcoder.community.dao.AlphaDao;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
@Service
//@Scope("prototype") // 默认单例:@Scope("singleton"),通常使用的也是单例Bean
public class AlphaService { // 容器管理其初始化及销毁的方法
@Autowired
private AlphaDao alphaDao;
public AlphaService() { // 构造器:实例化AlphaService
System.out.println("实例化AlphaService");
}
// 给该Bean增加初始化方法
@PostConstruct // 该注解表示初始化方法会在构造器之后调用
public void init() {
System.out.println("初始化AlphaService");
}
@PreDestroy // 在对象销毁之前调用该方法(销毁之后就没法调用了)
public void destroy() {
System.out.println("销毁AlphaService");
}
public String find() {
return alphaDao.select();
}
}
3.controller层
package com.nowcoder.community.controller;
import com.nowcoder.community.service.AlphaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller // Service、Repository,前面三个均由Component实现的,以上四个中的任意一个注解加到类上均会被扫描。区别在于语义上的区别
@RequestMapping("/alpha") // "/alpha":用于给下面的类AlphaController取一个访问的路径名。给浏览器提供一个服务,浏览器能够通过这个名字访问某个类中的某个方法。
public class AlphaController {
@Autowired
private AlphaService alphaService;
// 类中给浏览器提供一个访问服务的方法/能够处理浏览器请求的方法
@RequestMapping("/hello") // 方法也需要访问名
@ResponseBody
public String sayHello() {
return "Hello Spring Boot.";
}
@RequestMapping("/data") // 方法也需要访问名
@ResponseBody
public String getData() {
return alphaService.find();
}
}
Spring MVC入门
GET/POST请求
package com.nowcoder.community.controller;
import com.nowcoder.community.service.AlphaService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Path;
import java.util.*;
@Controller // Service、Repository,前面三个均由Component实现的,以上四个中的任意一个注解加到类上均会被扫描。区别在于语义上的区别
@RequestMapping("/alpha") // "/alpha":用于给下面的类AlphaController取一个访问的路径名。给浏览器提供一个服务,浏览器能够通过这个名字访问某个类中的某个方法。
public class AlphaController {
@Autowired
private AlphaService alphaService;
// 类中给浏览器提供一个访问服务的方法/能够处理浏览器请求的方法
@RequestMapping("/hello") // 方法也需要访问名
@ResponseBody
public String sayHello() {
return "Hello Spring Boot.";
}
@RequestMapping("/data") // 方法也需要访问名
@ResponseBody
public String getData() {
return alphaService.find();
}
/**
* 怎么获得请求对象?(封装着请求数据)
* 怎么获得相应对象?(封装着相应数据)
*/
@RequestMapping("/http")
public void http(HttpServletRequest request, HttpServletResponse response) {
// 获取请求数据
// 请求行
System.out.println(request.getMethod()); // 请求方法,第一行的数据
System.out.println(request.getServletPath()); // 请求路径,第一行的数据
// 请求头,若干行的数据
Enumeration<String> enumeration = request.getHeaderNames(); // 迭代器对象
while (enumeration.hasMoreElements()) {
String name = enumeration.nextElement(); // 取到的是请求行的key
String value = request.getHeader(name); // 将作为key的name传入获取相应的value
System.out.println(name + ":" + value);
}
// 请求体
System.out.println(request.getParameter("code"));
// 返回相应的数据
response.setContentType("text/thml;charset=utf-8"); // charset=utf-8/charset=gbk 都可以,但不要写错字符,不然会出现乱码
try (
PrintWriter writer = response.getWriter();
){
writer.write("<h1>资源社区<h1>");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// GET请求:传参方式一 ?拼接
// 查询所有的学生:/students?current=1&limit=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 + ":" + limit);
return "some students";
}
// GET请求:传参方式二 {}
// 查询一个学生id: /student/12345
@RequestMapping(path = "/student/{id}", method = RequestMethod.GET)
@ResponseBody
public String getStudent(@PathVariable("id") int id) {
System.out.println(id);
return "a student";
}
// POST请求(其实get请求也能传,但get请求在路径上明文传,请求的数量有限)
@RequestMapping(path = "/student", method = RequestMethod.POST)
@ResponseBody
public String submitStudent(String name, int number) {
System.out.println(name);
System.out.println(number);
return "success";
}
// 响应(动态的HTML数据)
// 方式一:举例:查询老师信息
@RequestMapping(path = "/teacther", method = RequestMethod.GET)
//@ResponseBody 不加该注解默认返回一个HTML
public ModelAndView getTeacher() {
ModelAndView modelAndView = new ModelAndView(); // 实例化对象:modelAndView 返回的是model和view的符合数据
modelAndView.addObject("name", "张三");
modelAndView.addObject("age", "30");
modelAndView.setViewName("/demo/view"); // 存放在template路径下,view表示view.html
return modelAndView;
}
// 方式二:举例:查询学校(该方式更简单一些)
@RequestMapping(path = "/school", method = RequestMethod.GET)
public String getSchool(Model model) {
model.addAttribute("age", 75);
model.addAttribute("name", "大连理工大学");
return "/demo/view";
}
// 响应JSON数据(异步请求:比如注册账号时输入信息被自动判断是否有效,其间并没有进行刷新)
@RequestMapping(path = "/emp", method = RequestMethod.GET)
@ResponseBody
public Map<String, Object> getEmp() {
Map<String, Object> emp = new HashMap<>();
emp.put("name", "张三");
emp.put("age", 25);
emp.put("offer", 0.00);
return emp;
}
// 几乎可以存储所有类型的表单数据
@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", 25);
emp.put("offer", 0.00);
list.add(emp);
emp = new HashMap<>();
emp.put("name", "张三");
emp.put("age", 25);
emp.put("offer", 0.00);
list.add(emp);
emp = new HashMap<>();
emp.put("name", "张三");
emp.put("age", 25);
emp.put("offer", 0.00);
list.add(emp);
return list;
}
}
MyBatis
user表学习:
id:用户排列值。
username:用户名
password:密码。会对用户密码进行加密,形成如图所示的password,salt则是在加密后的密码上拼接五位随机字符串,对拼好的随机字符串进行再加密,增强密码的保密强度。
salt:加盐,具体功能如上。
email:邮箱。
type:类型。0:普通用户,1:管理员,2:版主。
status:用户的状态。0:没有激活,1:已激活。
activation_code:激活码。
header_url:用户头像图片的访问路径。
create_time:用户创建(或注册)的时间。
pom.xml文件中引入MySQL:
网址: mvnrepository.com/artifact/my…
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
</dependency>
引入MyBatis:
网址:mvnrepository.com/artifact/or…
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
数据源和MyBatis相关配置
# DataSourceProperties --连接池(又称为数据源):能够管理连接的工厂。
# 作用一:能够统一的初始化一批连接,用于复用,效率高。
# 作用二:能够管理连接的上限,比如:maximum-pool-size=15,如果超过15则进行等待,避免数据库过多的人次访问导致的瘫痪。
# 数据库驱动
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据库连接路径:communities表示库名
spring.datasource.url=jdbc:mysql://localhost:3306/communities?characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong
spring.datasource.username=root
spring.datasource.password=zhangbo
# 连接池配置
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.maximum-pool-size=15
# 保留的最小连接数
spring.datasource.hikari.minimum-idle=5
# 超时时间,连接空闲了的线程等30000如果依旧空闲则进行关闭
spring.datasource.hikari.idle-timeout=30000
# MybatisProperties
mybatis.mapper-locations=classpath:mapper/*.xml
# 实体类:封装表的数据(entity表示实体类所在包的包名,在该包之下创建)
mybatis.type-aliases-package=com.nowcoder.community.entity
# 启动 id 主键自增长
mybatis.configuration.useGeneratedKeys=true
# 忽略大小写,比如:header_url 和 headerUrl 可以自动匹配,也即表中的字段和类中的属性自动匹配。
mybatis.configuration.mapUnderscoreToCamelCase=true
entity
提供User实体的属性,get、set方法及toString()方法。
package com.nowcoder.community.entity;
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 String createTime;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getActivationCode() {
return activationCode;
}
public void setActivationCode(String activationCode) {
this.activationCode = activationCode;
}
public String getHeaderUrl() {
return headerUrl;
}
public void setHeaderUrl(String headerUrl) {
this.headerUrl = headerUrl;
}
public String getCreateTime() {
return createTime;
}
public void setCreateTime(String createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + ''' +
", password='" + password + ''' +
", salt='" + salt + ''' +
", email='" + email + ''' +
", type=" + type +
", status=" + status +
", activationCode='" + activationCode + ''' +
", headerUrl='" + headerUrl + ''' +
", createTime='" + createTime + ''' +
'}';
}
}
Dao层接口:UserMapper
提供实体增删改查的方法:
package com.nowcoder.community.dao;
import com.nowcoder.community.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
//@Repository
/**
* 类名:MyBatis的开发通常叫xxxMapper
* 通常只需要写成接口,不需要写实现类
*/
@Mapper // MyBatis 提供的注解来标识该Bean、装配该Bean
public interface UserMapper {
User selectById(int id); // 根据id查user
User selectByName(String username); // 根据用户名查user
User selectByEmail(String email); // 根据邮箱查user
int insertUser(User user); // 插入用户,插入的是User用户一行的信息
int updateStatus(int id, int status); // 修改,条件是:id定位,结果:修改状态status
int updateHeader(int id, String headerUrl); // 修改头像,以id为条件,修改的是headerUrl
int updatePassword(int id, String password); // 修改密码,以id为条件,修改的是密码
}
编写mapper映射文件
官网引入格式:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
映射UserMapper接口中的方法,不再需要编写实现类。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nowcoder.community.dao.UserMapper">
<!-- <insert id="insertFields">-->
<!-- username, password, salt, email, type, status, activation_code, header_url, create_time-->
<!-- </insert>-->
<!-- 提炼字段内容-->
<sql id="selectFields">
id, username, password, salt, email, type, status, activation_code, header_url, create_time
</sql>
<!-- id="selectById":表示这部分SQL是为UserMapper中的selectById方法服务的 -->
<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 <include refid="selectFields"></include>
from user
where email = #{email}
</select>
<insert id="insertUser" parameterType="User" keyProperty="id">
insert into user (username, password, salt, email, type, status, activation_code, header_url, create_time)
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="updateHeader">
update user set header_url = #{headerUrl} where id = #{id}
</update>
<update id="updatePassword">
update user set password = #{password} where id = #{id}
</update>
</mapper>
测试:
package com.nowcoder.community;
import com.nowcoder.community.dao.UserMapper;
import com.nowcoder.community.entity.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Date;
@RunWith(SpringRunner.class)
@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 = userMapper.selectByEmail("nowcoder101@sina.com");
System.out.println(user);
user = userMapper.selectByName("liubei");
System.out.println(user);
}
@Test
public void testInsertUser() {
User user = new User();
user.setUsername("test");
user.setPassword("123456");
user.setSalt("abcdef");
user.setEmail("test@qq.com");
user.setHeaderUrl("http://www.nowcoder.com/101.png");
user.setCreateTime(new Date());
int rows = userMapper.insertUser(user);
System.out.println(rows);
System.out.println(user.getId());
}
@Test
public void testUpdateStatus() {
int rows = userMapper.updateStatus(150, 1);
System.out.println(rows);
rows = userMapper.updateHeader(150, "http://www.nowcoder.com/01.png");
System.out.println(rows);
rows = userMapper.updatePassword(150, "123654");
System.out.println(rows);
}
}