java-springboot

SSM部分的最后一块硬骨头

1. springboot概念

在这一部分,我们主要了解以下3个问题:

  1. 什么是SpringBoot

    springboot是spring快速开发脚手架,通过约定大于配置的方式,快速构建和启动spring项目

  2. 为什么要学习SpringBoot

    spring的缺点:

    复杂的配置,项目各种配置是开发时的损耗, 写配置挤占了写应用程序逻辑的时间。混乱的依赖管理。项目的依赖管理非常的繁琐。决定项目里要用哪些库就已经够让人头痛的了,你还要知道这些库的哪个版本和其他库不会有冲突,这是一个棘手的问题。并且,一旦选错了依赖的版本,随之而来的就是各种的不兼容的bug。
    springboot 可以解决上面2个问题

  3. SpringBoot的特点
    快速开发spring应用的框架内嵌tomcat和jetty容器,不需要单独安装容器,jar包直接发布一个web应用简化maven配置,parent这种方式,一站式引入需要的各种依赖基于注解的零配置思想和各种流行框架,spring web mvc,mybatis,spring cloud无缝整合更多细节,大家可以到官网查看。

  4. 总结
    spring boot 是spring快速开发脚手架,通过约定大于配置,优化了混乱的依赖管理,和复杂的配置,让我们用
    java -jar方式,运行启动java web项目

2. 入门案例

2.1 pom.xml

创建maven项目后

添加依赖

SpringBoot提供了一个名为spring-boot-starter-parent的构件,里面已经对各种常用依赖(并非全部)的版本进行了管理,我们的项目需要以这个项目为父工程,这样我

们就不用操心依赖的版本问题了,需要什么依赖,直接引入坐标即可

添加父工程坐标

1
2
3
4
5
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
</parent>

添加web启动器

为了让SpringBoot帮我们完成各种自动配置,我们必须引入SpringBoot提供的自动配置依赖,我们称为 启动器 。

因为我们是web项目,这里我们引入web启动器:

1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

需要注意的是,我们并没有在这里指定版本信息。因为SpringBoot的父工程已经对版本进行了管理了。
这个时候,我们会发现项目中多出了大量的依赖:

image-20220104155212134

这些都是SpringBoot根据spring-boot-starter-web这个依赖自动引入的,而且所有的版本都已经管理好,不会出现冲突。

管理jdk版本

默认情况下,maven工程的jdk版本是1.5,而我们开发使用的是1.8,因此这里我们需要修改jdk版本,只需要简单的添加以下属性即可:

1
2
3
<properties>
<java.version>1.8</java.version>
</properties>

完整pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.kkb</groupId>
<artifactId>day74_springboot</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

</project>

2.2 启动类

Spring Boot项目通过main函数即可启动,我们需要创建一个启动类:

image-20220104155623548

编写main函数:

1
2
3
4
5
6
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

2.3 编写controller

接下来,我们就可以像以前那样开发SpringMVC的项目了!
我们编写一个controller:

image-20220104155815772

1
2
3
4
5
6
7
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "hello, spring boot!";
}
}

启动测试
接下来,我们运行main函数,查看控制台:

image-20220104155914549

并且可以看到监听的端口信息:

image-20220104155932904

1)监听的端口是8080
2)SpringMVC的映射路径是:/
3) /hello 路径已经映射到了 HelloController 中的 hello() 方法
打开页面访问:http://localhost:8080/hello

image-20220104160002276

3. 全注解配置和属性注入

在入门案例中,我们没有任何的配置,就可以实现一个SpringMVC的项目了,快速、高效!
但是有同学会有疑问,如果没有任何的xml,那么我们如果要配置一个Bean该怎么办?比如我们要配置一个数据库
连接池,以前会这么玩:

1
2
3
4
5
6
7
<!-- 配置连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>

现在该怎么做呢?

3.1 spring全注解配置

spring全注解配置主要靠java类和一些注解,比较常用的注解有:

@Configuration :声明一个类作为配置类,代替xml文件

@Bean :声明在方法上,将方法的返回值加入Bean容器,代替 标签

@value :属性注入

@PropertySource :指定外部属性文件,

我们接下来用java配置来尝试实现连接池配置:

首先引入Druid连接池依赖:

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>

创建一个jdbc.properties文件,编写jdbc属性(可以拷贝):

1
2
3
4
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/lxs
jdbc.username=root
jdbc.password=123

然后编写代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
@Value("${jdbc.url}")
String url;
@Value("${jdbc.driverClassName}")
String driverClassName;
@Value("${jdbc.username}")
String username;
@Value("${jdbc.password}")
String password;
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(url);
dataSource.setDriverClassName(driverClassName);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
  • @Configuration :声明我们 JdbcConfig 是一个配置类
  • @PropertySource :指定属性文件的路径是: classpath:jdbc.properties
  • 通过 @Value 为属性注入值
  • 通过@Bean将 dataSource() 方法声明为一个注册Bean的方法,Spring会自动调用该方法,将方法的返回值加入Spring容器中。默认的对象名id=方法名,可以通过@Bean(“自定义名字”),来指定新的对象名然后我们就可以在任意位置通过 @Autowired 注入DataSource了!

我们在 HelloController 中测试:

1
2
3
4
5
6
7
8
9
@RestController
public class HelloController {
@Autowired
private DataSource dataSource;
@GetMapping("hello")
public String hello() {
return "hello, spring boot!" + dataSource;
}
}

然后Debug运行并查看:

image-20220104161218730

3.2 属性注入

SpringBoot的属性注入

在上面的案例中,我们实验了java配置方式。不过属性注入使用的是@Value注解。这种方式虽然可行,但是不够强大,因为它只能注入基本类型值。

在SpringBoot中,提供了一种新的属性注入方式,支持各种java基本数据类型及复杂类型的注入。

  1. 我们新建一个类,用来进行属性注入:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @ConfigurationProperties(prefix = "jdbc")
    public class JdbcProperties {
    private String url;
    private String driverClassName;
    private String username;
    private String password;
    // ... 略
    // getters 和 setters
    }
    • 在类上通过@ConfigurationProperties注解声明当前类为属性读取类

    • prefix=”jdbc” 读取属性文件中,前缀为jdbc的值。

    • 在类上定义各个属性,名称必须与属性文件中 jdbc. 后面部分一致

    • 需要注意的是,这里我们并没有指定属性文件的地址,所以我们需要把jdbc.properties名称改为application.properties,这是SpringBoot默认读取的属性文件名:

      image-20220104161502764

  2. 在JdbcConfig中使用这个属性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Configuration
    @EnableConfigurationProperties(JdbcProperties.class)
    public class JdbcConfig {
    @Bean
    public DataSource dataSource(JdbcProperties jdbc) {
    DruidDataSource dataSource = new DruidDataSource();
    dataSource.setUrl(jdbc.getUrl());
    dataSource.setDriverClassName(jdbc.getDriverClassName());
    dataSource.setUsername(jdbc.getUsername());
    dataSource.setPassword(jdbc.getPassword());
    return dataSource;
    }
    }
    • 通过 @EnableConfigurationProperties(JdbcProperties.class) 来声明要使用 JdbcProperties 这个类的对象
      然后你可以通过以下方式注入JdbcProperties:

      • @Autowired注入

        1
        2
        @Autowired
        private JdbcProperties prop;
      • 构造函数注入

        1
        2
        3
        4
        private JdbcProperties prop;
        public JdbcConfig(Jdbcproperties prop){
        this.prop = prop;
        }
      • 声明有@Bean的方法参数注入

        1
        2
        3
        4
        @Bean
        public Datasource dataSource(JdbcProperties prop){
        // ...
        }
  3. 测试结果:

    image-20220104162125375

  4. 大家会觉得这种方式似乎更麻烦了,事实上这种方式有更强大的功能,也是SpringBoot推荐的注入方式。两者对比关系:

    image-20220104162218656

    优势:
    Relaxed binding:松散绑定

    不严格要求属性文件中的属性名与成员变量名一致。支持驼峰,中划线,下划线等等转换,甚至支持对象引导。比如:user.friend.name:代表的是user对象中的friend属性中的name属性,显然friend也是对象。@value注解就难以完成这样的注入方式。

    meta-data support:元数据支持,帮助IDE生成属性提示(写开源框架会用到)。

3.3 更优雅的注入

事实上,如果一段属性只有一个Bean需要使用,我们无需将其注入到一个类(JdbcProperties)中。而是直接在需要的地方声明即可:

1
2
3
4
5
6
7
8
9
10
@Configuration
public class JdbcConfig {
@Bean
// 声明要注入的属性前缀,SpringBoot会自动把相关属性通过set方法注入到DataSource中
@ConfigurationProperties(prefix = "jdbc")
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
return dataSource;
}
}

4. 自动配置原理

通过刚才的案例看到,一个整合了SpringMVC的WEB工程开发,变的无比简单,那些繁杂的配置都消失不见了,这是如何做到的?

这些都是从springboot启动器开始

image-20220104162615241

5. 整合springmvc

刚才案例已经能实现mvc自动配置,这里我们主要解决以下3个问题

修改端口

查看SpringBoot的全局属性可知,端口通过以下方式配置:

1
2
# 映射端口
server.port=80

重启服务后测试:

image-20220104162926227

访问静态资源

ResourceProperties的类,里面就定义了静态资源的默认查找路径:

image-20220104162949949

默认的静态资源路径为:

  • classpath:/META-INF/resources/
  • classpath:/resources/
  • classpath:/static/
  • classpath:/public

只要静态资源放在这些目录中任何一个,SpringMVC都会帮我们处理。

我们习惯会把静态资源放在 classpath:/static/ 目录下。我们创建目录,并且添加一些静态资源:

image-20220104163053365

添加拦截器

拦截器也是我们经常需要使用的,在SpringBoot中该如何配置呢?
首先我们定义一个拦截器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class LoginInterceptor implements HandlerInterceptor {
private Logger logger = LoggerFactory.getLogger(LoginInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Objecthandler) {
logger.debug("处理器执行前执行!");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object
handler, ModelAndView modelAndView) {
logger.debug("处理器执行后执行!");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object
handler, Exception ex) {
logger.debug("跳转后执行!");
}
}

通过实现 WebMvcConfigurer 并添加 @Configuration 注解来实现自定义部分SpringMvc配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
public class MvcConfig implements WebMvcConfigurer{
/**
* 通过@Bean注解,将我们定义的拦截器注册到Spring容器
* @return
*/
@Bean
public LoginInterceptor loginInterceptor(){
return new LoginInterceptor();
}
/**
* 重写接口中的addInterceptors方法,添加自定义拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 通过registry来注册拦截器,通过addPathPatterns来添加拦截路径
registry.addInterceptor(this.loginInterceptor()).addPathPatterns("/**");
}
}

ant path路径匹配通配符

  • ‘?’ 匹配任何单字符
  • ‘*’ 匹配0或者任意数量的字符
  • ‘/**’ 匹配0或者更多的目录

结构如下:

image-20220104165030011

接下来运行并查看日志:
你会发现日志中什么都没有,因为我们记录的log级别是debug,默认是显示info以上,我们需要进行配置。
SpringBoot通过 logging.level.=debug 来配置日志级别,填写包名

1
2
# 设置com.lxs包的日志级别为debug
logging.level.com.lxs=debug

6. 整合jdbc

导入资料中的t_user.sql文件
引入依赖

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>

当然,不要忘了数据库驱动,SpringBoot并不知道我们用的什么数据库,这里我们选择MySQL:

1
2
3
4
5
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>

配置连接池
其实,在刚才引入jdbc启动器的时候,SpringBoot已经自动帮我们引入了一个连接池:

image-20220104165632219

因此,我们只需要指定连接池参数即可:

1
2
3
4
5
6
7
8
9
# 连接四大参数
spring.datasource.url=jdbc:mysql://localhost:3306/springboot
spring.datasource.username=root
spring.datasource.password=123
# 可省略,SpringBoot自动推断
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.hikari.idle-timeout=60000
spring.datasource.hikari.maximum-pool-size=30
spring.datasource.hikari.minimum-idle=10

实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class User implements Serializable {
private Long id;
// 用户名
//自动转换下换线到驼峰命名user_name -> userName
private String userName;
// 密码
private String password;
// 姓名
private String name;
// 年龄
private Integer age;
// 性别,1男性,2女性
private Integer sex;
// 出生日期
private Date birthday;
// 创建时间
private Date created;
// 更新时间
private Date updated;
// 备注
private String note;

dao

1
2
3
4
5
6
7
8
@Repository
public class JdbcDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public List<User> findAll() {
return jdbcTemplate.query("select * from tb_user", new BeanPropertyRowMapper<>(User.class));
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
@RunWith(SpringRunner.class)
@SpringBootTest
public class JdbcDaoTest {
@Autowired
private JdbcDao jdbcDao;
@Test
public void findAll() {
List<User> list = jdbcDao.findAll();
for (User user : list) {
System.out.println(user);
}
}
}

7. mybatis

SpringBoot官方并没有提供Mybatis的启动器,不过Mybatis官网自己实现了:

1
2
3
4
5
6
<!--mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>

配置,基本没有需要配置的:

1
2
3
4
# mybatis 别名扫描
mybatis.type-aliases-package=com.lxs.domain
# mapper.xml文件位置,如果没有映射文件,请注释掉
mybatis.mapper-locations=classpath:mappers/*.xml

实体类,直接使用jdbc用到的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class User {
private Long id;
// 用户名
//自动转换下换线到驼峰命名user_name -> userName
private String userName;
// 密码
private String password;
// 姓名
private String name;
// 年龄
private Integer age;
// 性别,1男性,2女性
private Integer sex;
// 出生日期
private Date birthday;
// 创建时间
private Date created;
// 更新时间
private Date updated;
// 备注
private String note;
//getter和setter方法

接口

1
2
3
public interface UserDao {
public List<User> findAll();
}

映射文件

1
2
3
4
5
6
7
8
9
<?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.lxs.demo.dao.UserDao">
<select id="findAll" resultType="user">
select * from tb_user
</select>
</mapper>

Mapper的加载接口代理对象方式有2种
第一种:使用@Mapper注解(不推荐)
需要注意,这里没有配置mapper接口扫描包,因此我们需要给每一个Mapper接口添加 @Mapper 注解,才能被识别。

1
2
3
@Mapper
public interface UserMapper {
}

第二种设置MapperScan,注解扫描的包(推荐)
@MapperScan(“dao所在的包”),自动搜索包中的接口,产生dao的代理对象

1
2
3
4
5
6
7
@SpringBootApplication
@MapperScan("com.lxs.demo.dao")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

测试
引入测试构建

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>

测试代码

1
2
3
4
5
6
7
8
9
10
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserDaoTest {
@Autowired
private UserDao userDao;
@Test
public void testFindAll() {
List<User> list = userDao.findAll();
}
}

7.1 通用mapper

概念

使用Mybatis时,最大的问题是,要写大量的重复SQL语句在xml文件中,除了特殊的业务逻辑SQL语句之外,还有大量结构类似的增删改查SQL。而且,当数据库表结

构改动时,对应的所有SQL以及实体类都需要更改。这大量增加了程序员的负担。避免重复书写CRUD映射的框架有两个

  • 通用mybatis(tk mybatis)

  • mybatis plus 更加强大,后面实战项目中讲解

通用Mapper的作者也为自己的插件编写了启动器,我们直接引入即可:

1
2
3
4
5
6
<!-- 通用mapper -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.0.2</version>
</dependency>

实体类
tk mybatis 实体类使用的注解是jpa注解

1
2
3
4
5
6
7
8
9
@Table(name = "tb_user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 用户名
private String userName;
....

注意事项:

  1. 默认表名=类名,字段名=属性名
  2. 表名可以使用 @Table(name = “tableName”) 进行指定
  3. @Column(name = “fieldName”) 指定
  4. 使用 @Transient 注解表示跟字段不进行映射

不需要做任何配置就可以使用了。

1
2
3
4
@Mapper
public interface UserMapper extends tk.mybatis.mapper.common.Mapper<User>{
public List<User> findByUser(User user);
}

自定义映射文件

映射复杂方法 resources/mappers/UserMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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.lxs.demo.dao.UserMapper">
<select id="findByUser" resultType="user">
SELECT
*
FROM
tb_user
<where>
<if test="name != null">
name like '%${name}%'
</if>
<if test="note != null">
and note like '%${note}%'
</if>
</where>
</select>
</mapper>

一旦继承了Mapper,继承的Mapper就拥有了Mapper所有的通用方法:

  1. Select

    方法: List select(T record); 说明:根据实体中的属性值进行查询,查询条件使用等号

    方法: T selectByPrimaryKey(Object key); 说明:根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号

    方法: List selectAll(); 说明:查询全部结果,select(null)方法能达到同样的效果

    方法: T selectOne(T record); 说明:根据实体中的属性进行查询,只能有一个返回值,有多个结果是抛出异 常,查询条件使用等号

    方法: int selectCount(T record); 说明:根据实体中的属性查询总数,查询条件使用等号

  2. Insert

    方法: int insert(T record); 说明:保存一个实体,null的属性也会保存,不会使用数据库默认值

    方法: int insertSelective(T record); 说明:保存一个实体,null的属性不会保存,会使用数据库默认值

  3. Update

    方法: int updateByPrimaryKey(T record); 说明:根据主键更新实体全部字段,null值会被更新

    方法: int updateByPrimaryKeySelective(T record); 说明:根据主键更新属性不为null的值

  4. Delete

    方法: int delete(T record); 说明:根据实体属性作为条件进行删除,查询条件使用等号
    方法: int deleteByPrimaryKey(Object key); 说明:根据主键字段进行删除,方法参数必须包含完整的主键属性

  5. Example

    方法: List selectByExample(Object example); 说明:根据Example条件进行查询 重点:这个查询支持通过 Example 类指定查询列,通过 selectProperties 方法指定查询列

    方法: int selectCountByExample(Object example); 说明:根据Example条件进行查询总数

    方法: int updateByExample(@Param(“record”) T record, @Param(“example”) Object example); 说明:根据

  6. Example条件更新实体 record 包含的全部属性,null值会被更新

    方法: int updateByExampleSelective(@Param(“record”) T record, @Param(“example”) Object example); 说明:根据Example条件更新实体 record 包含的不是null的属性值

    方法: int deleteByExample(Object example); 说明:根据Example条件删除数据

注意要把MapperScan类改成tk-mybatis构件的类

1
2
3
4
5
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@EnableConfigurationProperties
@MapperScan("com.lxs.demo.dao")
public class Application {

https://www.jianshu.com/p/a734c549fbd4