PHP转JAVA学习遇到的一系列问题记录

2023-10-18 16:46:25 浏览数 (1)

前言

之前做项目都是照葫芦画瓢,从来没系统性的学习过java、spring、springboot,现在下定决心从0开始学习,本文章只为记录个人遇到的一系列问题,并直接写出来以加深印象。

java部分和spring部分没记录,直接从spring boot开始到spring cloud结束。

知识点

  • 用idea初始化spring boot项目后,maven装的特别慢:需要配置maven镜像。参考具体配置
    • 创建了一个yml格式的配置文件application.yml,发现中文注释报错,需要设置项目的文件编码都为utf-8。参考具体配置
    • 使用@RequestMapping发现所有接口自动跳转login页面,注释pom文件中的spring-boot-starter-security,因为它默认所有接口收到保护
    • 写了一个接口发现报错:请检查控制器是否添加了@RestController注解
    • 写了一个bean,发现idea提示未配置 Spring Boot 配置注解处理器, 需要pom添加配置spring-boot-configuration-processor即可解决。参考具体配置

自定义获取默认配置项的bean

  • 写了一个获取默认配置项的bean,必须保证Java Bean的属性名称与配置一致,然后,添加两个注解@Configuration@ConfigurationProperties,就能自动对应配置文件的数据。 使用的时候,必须使用@Autowired自动注入,不能自己new出来,因为要通过springboot来处理注解等框架的东西。

如果标注@Configuration,则可通过Spring Boot的自动扫描机制自动加载,否则,使用@Import在启动入口引入手动加载

代码语言:javascript复制
// application.yml配置文件
spring:
  data:
    redis:
      host: ${REDIS_HOST:localhost}
      port: ${REDIS_PORT:6379}
      password: ${REDIS_PASSWORD:}
      database: ${REDIS_DATABASE:0}
代码语言:javascript复制
// bean文件夹
@Data
@Configuration
@ConfigurationProperties("spring.data.redis")
public class RedisConfigBean {
    private String host;
    private int port;
    private String password;
    private int database;
}
代码语言:javascript复制
// 控制器使用 使用Bean来获取配置项,必须使用@Autowired自动注入
    @Autowired
    RedisConfigBean redisConfigBean;

    @Operation(summary = "Redis测试")
    @PostMapping(value = "/redis")
    public void list2() {
        System.out.println(redisConfigBean.getPort());
    }

使用@Bean注解来注册bean

  • 或者使用@Bean注解来注册一个bean:将第三方类,按照使用Bean的方式注册到容器中,只会调用一次,在其他地方就可以跟正常使用bean一样使用,先自动注入,然后就是使用。

跟上文中主要区别就是需要用@Bean进行声明,并且没有@Configuration注解

代码语言:javascript复制
// 比如有个class,不能自己添加比如@Configuration等其他注解

public class SomeClass {
    private int a = 1;
    private int b = 2;

    public int getA() {
        return a*100;
    }

    public int getB() {
        return b*100;
    }
}
代码语言:javascript复制
// 想让他变成一个bean注册到容器中,在config或者其他文件夹加一个挂载类
@Component
public class ThirdPartBean {
    @Bean
    public SomeClass someClass() {
        return new SomeClass();
    }
}

// 然后就可以在任何地方使用了,跟使用bean一样
@RestController
@RequestMapping("/third")
public class ThirdController {

    @Autowired
    SomeClass someClass;

    @GetMapping(value = "/getbean", produces = "application/json")
    public void getBean() {
        System.out.println(someClass.getA());
    }
}

常见注解的理解

  • @Repository、@Component、@Service、@Controller之间的区别与联系具体参考文档:
代码语言:javascript复制
在Spring2.5版本中,引入了更多的Spring类注解:@Component,@Service,@Controller。
@Component是一个通用的Spring容器管理的单例bean组件。而@Repository, @Service, @Controller就是针对不同的使用场景所采取的特定功能化的注解组件。

当你的一个类被@Component所注解,那么就意味着同样可以用@Repository, @Service, @Controller来替代它:

@Component最普通的组件,可以被注入到spring容器进行管理
@Repository作用于持久层,作为DAO对象(数据访问对象,Data Access Objects),可以直接对数据库进行操作
@Service作用于业务逻辑层,处理业务逻辑
@Controlle作用于表现层(spring-mvc的注解),前端请求的处理,转发,重定向

总结:@Service, @Controller, @Repository = {@Component   一些特定的功能}。这个就意味着这些注解在部分功能上是一样的。都属于spring注解,注解后可以被spring框架所扫描并注入到spring容器来进行管理。

使用swagger生成接口文档

swagger3基于openApi3,大部分注解都跟老的不一样了,参考 https://blog.csdn.net/qq_35425070/article/details/105347336

  • 添加接口文档包,swagger
代码语言:javascript复制
<!-- 接口文档 swagger UI 本地访问 http://localhost:8080/swagger-ui/index.html-->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.1.0</version>
</dependency>
代码语言:javascript复制
// 代码示例
@Operation(summary = "测试自动加载通过@Bean注入的bean")
@GetMapping(value = "/getbean", produces = "application/json")
public void getBean() {
    System.out.println(someClass.getA());
}

@Operation(summary = "swagger标题摘要:path参数以及url参数", description = "下方描述:访问示例http://localhost:8080/third/list1/12?sort=11212")
@GetMapping(value = "/list1/{id}", produces = "application/json")
public List list1(
        @PathVariable(value = "id", required = true) @Parameter(description = "path参数") int id,
        @RequestParam(value = "sort", required = false, defaultValue = "0") @Parameter(description = "url参数") int sort
) {
    List list = new ArrayList();
    list.add("A");
    list.add("B");
    list.add(32);
    list.add("32");
    list.add(id);
    list.add(sort);
    System.out.println(list);
    return list;
}

Maven 相关知识

代码语言:javascript复制
    // # 模块管理
    // 在软件开发中,把一个大项目拆分成多个模块是降低软件复杂度的有效方法
    // 对于Maven来说,原来是一个大项目
    //
    //single-project
    //├── pom.xml
    //└── src
    //
    // 现在可以分拆成三个模块
    //
    //mutiple-project
    //├── module-a
    //│   ├── pom.xml
    //│   └── src
    //├── module-b
    //│   ├── pom.xml
    //│   └── src
    //└── module-c
    //    ├── pom.xml
    //    └── src

    // Maven可以有效的管理多个模块,我们只需要把每个模块当作一个独立的Maven项目,他们有各自独立的pom.xml
    // - 首先创建一个parent项目,把abc项目中重复的提取出来,他继承springframework,并定好各个包的版本号以及引入共同依赖包(<packaging>pom</packaging>)
    // - 之后,其他项目比如a,b,c就可以继承parent项目(<packaging>jar</packaging>)
    // - 最后,在编译的时候,还要在根目录创建一个pom.xml(也就是build pom)统一编译,其中规定了 <modules>,比如
    //    <modules>
    //        <module>parent</module>
    //        <module>module-a</module>
    //        <module>module-b</module>
    //        <module>module-c</module>
    //    </modules>
    //
    // 这样,在根目录执行 mvn clean package时,maven根据根目录的pom.xml找到包括parent在内的4个<module>,一次性编译

JDBC和连接池

  • JDBC链接: JDBC是一套接口规范,类似PHP的PDO,是协议,我们把数据库实现了JDBC接口的jar包称为JDBC驱动,比如mysql的包mysql-connector-java
代码语言:javascript复制
jdbc:mysql://<hostname>:<port>/<db>?key1=value1&key2=value2
jdbc:mysql://localhost:3306/fmock?useSSL=false&characterEncoding=utf8
  • 连接池:目前使用最广泛的就是hikariCP
代码语言:javascript复制
    // 1. 首先
    // 添加他的依赖
    // <dependency>
    //    <groupId>com.zaxxer</groupId>
    //    <artifactId>HikariCP</artifactId>
    //    <version>4.0.3</version>
    //    <type>bundle</type>
    // </dependency>

    // 2.接着
    // 获取数据库连接
    @SneakyThrows
    public static Connection getConnection() {
        String JDBC_URL = "jdbc:mysql://localhost:3306/fmock";
        String JDBC_USER = "root";
        String JDBC_PWD = "root";

        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(JDBC_URL);
        config.setUsername(JDBC_USER);
        config.setPassword(JDBC_PWD);

        config.addDataSourceProperty("connectionTimeout", "1000"); // 连接超时:1秒
        config.addDataSourceProperty("idleTimeout", "60000");      // 空闲超时:60秒
        config.addDataSourceProperty("maximumPoolSize", "10");     // 最大连接数:10

        // 注意创建DataSource也是一个非常昂贵的操作,所以通常DataSource实例总是作为一个全局变量存储,并贯穿整个应用程序的生命周期
        DataSource ds = new HikariDataSource(config);

        // 有了连接池以后,我们如何使用它呢?
        // 和前面的代码类似,只是获取Connection时,把DriverManage.getConnection()改为ds.getConnection()
        // 第一次调用ds.getConnection(),会迫使连接池内部先创建一个Connection,再返回给客户端使用。
        // 当我们调用conn.close()方法时(在try(resource){...}结束处),不是真正“关闭”连接,而是释放到连接池中,以便下次获取连接时能直接返回
        Connection conn = ds.getConnection();
        // Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PWD);

        return conn;
    }

    // 关闭数据库连接(释放到连接池)
    public static void closeConn(Connection conn) {
        try {
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

自动生成getter和setter方法的包:lombok

  • 自动在类上生成getting()和setting()方法:lombok包
代码语言:javascript复制
// pom中添加如下依赖即可,版本号统一配置就行,在parent的dependencyManagement里有
// 注意:dependencyManagement中定义的只是依赖的声明,并不实现引入,因此子项目需要显式的声明需要用的依赖
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

// 添加完后,在类上添加 `@Data` 注解即可

关于pom配置文件抽离parent,官方spring-boot就是这么做的

  • pom使用parent统一管理的好处:properties和dependencyManagement中统一定义了依赖包的版本号

参考链接

代码语言:javascript复制
如果有多个子项目都引用同一样依赖,则可以避免在每个使用的子项目里都声明一个版本号。
当想升级或切换到另一个版本时,只需要在顶层父容器里更新,而不需要逐个修改子项目;另外如果某个子项目需要另外的一个版本,只需要声明version即可。

想使用redis:

首先,redis/jedis是redis官方推出的面向JAVA的客户端; 其次,Spring-data-redis是spring大家族的一部分,包含了jedis依赖,同时还有很多其他包,有连接池自动管理功能,提供高度封装的RedisTemplate类,对jedis大量api进行归类封装,并提供很多便捷化方法; 然后,spring-boot-starter-data-redis是spring-boot的包,其中就包含了spring-data-redis依赖和lettuce等; 最后Redisson,Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。 (在spring-boot项目中,可以直接用专门的包redisson-spring-boot-starter,直接帮你了很多autoconfig的事,它里面就有spring-boot-starter-data-redis,所以可以用RedisTemplate类,清晰了吧。 当然,你也可以直接用Redisson,然后自己进行配置,new对象使用。就好比laravel的一些包,可以直接用"ORM"包,也可以用"Larave-ORM"的专属包)

代码语言:javascript复制
// 引入依赖
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.23.4</version>
</dependency>
代码语言:javascript复制
// 默认只有`RedisTemplate<Object, Object>`  和  `RedisTemplate<String, String>`, 所以需要
// 配置一个`RedisTemplate<String, Object>`类型的Bean,并初始序列化方式,否则存入的redis会有乱码
@Configuration
public class RedisTemplateConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {

        RedisTemplate<String, Object> template = new RedisTemplate<>();
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();

        template.setConnectionFactory(factory);
        //key序列化方式
        template.setKeySerializer(redisSerializer);
        //value序列化
        template.setValueSerializer(redisSerializer);
        //value hashmap序列化
        template.setHashValueSerializer(redisSerializer);
        //key haspmap序列化
        template.setHashKeySerializer(redisSerializer);

        return template;
    }
}
代码语言:javascript复制
// 使用
@RestController()
@RequestMapping(value = "four")
public class FourController {

    // Java变量的初始化顺序为:静态变量或静态语句块 –> 实例变量或初始化语句块 –> 构造方法 –> @Autowired

    private RedisService redisService;

    /* 构造方法注入
    public FourController(RedisService redisService) {
        this.redisService = redisService;
    }
    */

    // 推荐使用基于set的自动注入,可以在构造函数执行之后才自动注入
    @Autowired
    private void init(RedisService redisService) {
        this.redisService = redisService;
    }

    @GetMapping(value = "redis1")
    public void redis1() {
        System.out.println(redisService.get("key111"));
        System.out.println(1221);
    }
}

使用数据库mysql spring-boot-starter-jdbc

1.首先mysql-connector-j的前身是mysql-connector-java,是MySQL提供的JDBC驱动包drive,实现了java.sql.Driver的规范,换数据库直接换drive即可。 2.spring-boot-starter-jdbcspring-boot-starter-data-jdbc都是springboot提供的,前者是基础包,后者是升级版(是data系列的包,同样的还有data-redis,data-elasticsearch等)。 3.mybatis-spring-boot-starter包含spring-boot-starter-jdbc,不再需要单独引入spring-boot-starter-jdbc了。 4.mybatis是个第三方包,任何框架都能使用,mybatis-spring-boot-starter是针对spring-boot专门配置的一个包,包含了starter自动装载等功能。 5.Spring Boot作为Spring的集大成者,spring-jdbcspring-data-jdbc 就是spring-boot-starter-jdbcspring-boot-starter-data-jdbc在spring里的东西。

代码语言:javascript复制
// 先引入依赖包
<!--  数据库相关 mysql-connector-j是驱动包,使用mysql必须装。driver-class-name中可以指定  -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

<!--  这个包里就引入了spring-jdbc, 使用JdbcTemplate  -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
代码语言:javascript复制
// application.yml添加数据库配置
spring:
  application:
    name: ${APP_NAME:我的测试JAVA项目}
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/fmock
    username: root
    password: root
代码语言:javascript复制
// 已经能使用了,随便写个组件,
// 增删改都是jdbcTemplate.update方法,只有查询方法不一样,有query,queryForObject,queryForList等
@Component
public class DBService {
    @Autowired
    JdbcTemplate jdbcTemplate;

    // 在JdbcTemplate中,除了查询有几个API之外,新增、删除与修改统一都使用update来操作,传入SQL即可.
    // update方法的返回值就是SQL执行受影响的行数.

    // 插入单条
    public int addUser(User user) {
        String sql = "insert into users(uuid, email, mobile, name, password) value (?, ?, ?, ?, ?)";

        // return jdbcTemplate.update(sql);
        return jdbcTemplate.update(sql, user.getUuid(), user.getEmail(), user.getMobile(), user.getName(), user.getPassword());
    }

    // 批量插入
    public int[] batchAddUser(User user) {
        String sql = "insert into users(uuid, email, mobile, name, password) value (?, ?, ?, ?, ?)";

        List<Object[]> batchValue = new ArrayList<Object[]>();
        batchValue.add(new Object[]{"uuid-tets", "8888@qq.com", "12111111111", "测试jdbctemplate", "xxxxxxxx"});
        batchValue.add(new Object[]{user.getUuid(), user.getEmail(), user.getMobile(), user.getName(), user.getPassword()});

        return jdbcTemplate.batchUpdate(sql,batchValue);
    }

    // 更新数据
    public int updateUser(User user) {
        String sql = "update users set name = ?, email = ? where id = ?";
        return jdbcTemplate.update(sql, "修改后的名字", user.getEmail(), user.getId());
    }

    // 删除
    public int delUser(long id){
        String sql = "delete from users where id = ?";
        return jdbcTemplate.update(sql, id);
    }

    // 查询,查询就不使用update方法了
    // queryForObject查询的结果不能空,且只能1条数据:nullableSingleResult
    // 使用new BeanPropertyRowMapper<>(User.class)对返回的数据进行封装,它通过名称匹配的方式,自动将数据列映射到指定类的实体类中(如果列名和属性名不同,就需要开发者自己实现 RowMapper 接口)
    public User find(long id) {
        String sql = "select * from users where id = ?";
        RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class);
        return jdbcTemplate.queryForObject(sql, rowMapper, id);
    }

    // queryForList返回多条或者0条都可以
    public List<Map<String, Object>> find2(long id) {
        String sql = "select * from users where id > ?";
        return jdbcTemplate.queryForList(sql, id);
    }
    // 直接使用query方法查询,能自定义映射类型rowMapper
    public List<User> find3(long id) {
        String sql = "select * from users where id > ?";
        RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class);
        return jdbcTemplate.query(sql, rowMapper, id);
    }
}
代码语言:javascript复制
// 控制器调用测试
@RestController()
@RequestMapping(value = "four")
public class FourController {

    // Java变量的初始化顺序为:静态变量或静态语句块 –> 实例变量或初始化语句块 –> 构造方法 –> @Autowired

    private RedisService redisService;

    private DBService dbService;

    /* 构造方法注入
    public FourController(RedisService redisService) {
        this.redisService = redisService;
    }
    */

    // 推荐使用基于set的自动注入,可以在构造函数执行之后才自动注入
    @Autowired
    private void init(RedisService redisService, DBService dbService) {
        this.redisService = redisService;
        this.dbService = dbService;
    }

    @GetMapping(value = "redis1")
    public void redis1() {
        System.out.println(redisService.get("key111"));
        System.out.println(1221);
    }

    @GetMapping(value = "db2")
    public int addUser() {
        User user = new User();
        user.setMobile("13333333333");
        user.setEmail("13333333333@qq.com");
        user.setName("name-db2");
        user.setPassword("xxxx");
        user.setUuid("uuid-xxxx");
        return dbService.addUser(user);
    }

    @GetMapping(value = "db3")
    public int[] batchAddUser() {
        User user = new User();
        user.setMobile("13333333333");
        user.setEmail("13333333333@qq.com");
        user.setName("name-db2");
        user.setPassword("xxxx");
        user.setUuid("uuid-xxxx");
        return dbService.batchAddUser(user);
    }

    @GetMapping(value = "db4")
    public int updateUserEmail() {
        User user = new User();
        user.setEmail("fffffff@qq.com");
        user.setId(37);
        return dbService.updateUser(user);
    }

    @Operation(summary = "根据ID删除某个用户")
    @GetMapping(value = "db5/{id}", produces = "application/json")
    public int delUserById(
            @PathVariable(value = "id") @Parameter(description = "待删除的用户ID") long id
    ) {
        return dbService.delUser(id);
    }

    @Operation(summary = "使用queryForObject查询单个用户")
    @GetMapping(value = "db6/{id}", produces = "application/json")
    public User findUserById(
            @PathVariable(value = "id") @Parameter(description = "查询ID") long id
    ) {
        return dbService.find(id);
    }

    @Operation(summary = "queryForList返回多条或者0条都可以")
    @GetMapping(value = "db7/{id}", produces = "application/json")
    public List<Map<String, Object>> getUserList(
            @PathVariable(value = "id") @Parameter(description = "查询大于ID的列表") long id
    ) {
        List<Map<String, Object>> list = dbService.find2(id);
        return list;
    }

    @Operation(summary = "query返回多条或者0条都可以")
    @GetMapping(value = "db8/{id}", produces = "application/json")
    public List<User> getUserListQuery(
            @PathVariable(value = "id") @Parameter(description = "查询大于ID的列表") long id
    ) {
        List<User> list = dbService.find3(id);
        return list;
    }
}

JPA : Java Persistence API

JPA就是JavaEE的一个ORM标准,他只规定了接口,所以还需要选择一个实现产品,跟JDBC接口和Mysql驱动一个意思。 HibernateMyBatis都是优秀的第三方ORM框架,我们即可以直接使用JPA也就是jakarta.persistence这个“标准”包,也可以用第三方包如MyBatis等。

MyBatis & MyBatis-plus:ORM框架

对比Spring提供的JdbcTemplate和ORM框架,有如下差别:

  1. 查询后需要手动提供mapper实例,以便把ResultSet的每一行变成Java对象(自定义映射类型rowMapper)。
  2. 增删改操作,所有需要的参数要手动传入,比如传参user.id/user.name等这样。
  3. 但是JdbcTemplate的优势是确定性,即每次读取操作一定是数据库操作而不是缓存,确定是代码比较繁琐。

介于全自动ORM如Hibernate,和手写全部SQL的比如JdbcTemplate之间,还有一种半自动的ORM,它只负责把ResultSet自动映射到JavaBean,或者自动填充JavaBean参数,但仍需要自己写Sql, MyBatis就是这种半自动化ORM框架。

首先需要引入mybatis-spring-boot-starter包,这个包里有mybatis-springmybatis, 还有spring-boot-starter-jdbc,以及自动配置的一些starter、autoconfigure包。

代码语言:javascript复制
// 首先,spring-boot项目中使用的话,需要安装 `MyBatis-Spring-Boot-Starter` 依赖,之后就可以使用`@Mapper`注解(默认搜寻带有 @Mapper 注解的 mapper 接口)
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.2</version>
</dependency>
代码语言:javascript复制
// 创建一个mapper比如/mapper/UserMapper.java,注意mapper必须是接口
// `@Select`中即可以写sql,支持使用`#{}`占位符参数,多个参数时可以用`@Param`进行标记匹配,示例如下:
// `@Select("SELECT * FROM users LIMIT #{offset}, #{maxResults}")`
// `List<User> getAll(@Param("offset") int offset, @Param("maxResults") int maxResults);`
package com.example.springbootstudy.mapper;

import com.example.springbootstudy.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;

@Mapper
public interface UserMapper {
    @Select(value = "select count(id) from users")
    int getAllCount();

    @Select("select name from users where id = #{id}")
    String getUserName(long id);

    // mybatis会根据方法的返回类型自动把ResultSet的每一行转换为User实例,不需要像jdbc那样使用RowMapper手动映射
    // 如果列名和属性名不同,可以通过select语句的别名`as`满足匹配
    @Select("select * from users where id = #{user_id}")
    User getUserInfo(@Param("user_id") long id);

    @Select("select * from users where id > #{id}")
    List<User> getUserList(@Param("id") long id);

    // 使用@Options注解,可以获取插入后的id,通过user对象
    @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
    @Insert("insert into users(uuid, email, mobile, password, name) values(#{userObj.uuid}, #{userObj.email}, #{userObj.mobile}, #{userObj.password}, #{userObj.name})")
    void addUser(@Param("userObj") User user);

    // 更新
    @Update("UPDATE users SET name = #{user.name}, createdAt = #{user.createdAt} WHERE id = #{user.id}")
    void update(@Param("user") User user);
    // 删除
    @Delete("DELETE FROM users WHERE id = #{id}")
    void deleteById(@Param("id") long id);
}
代码语言:javascript复制
// 创建一个控制器,依赖注入mapper,即可简单调用mybatis
@RestController
@RequestMapping(value = "/five", name = "测试mybatis的控制器")
public class FiveController {

    private final UserMapper userMapper;
    
    // 将mapper依赖注入
    public FiveController(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @GetMapping(value = "mb1", name = "测试方法mb1")
    public int mb1() {
        System.out.println("mb1");
        return userMapper.getAllCount();
    }

    @Operation(summary = "获取用户名字")
    @GetMapping(value = "mb2")
    public String mb2(
            @RequestParam(value = "id") @Parameter(description = "url参数") long id
    ) {
        return userMapper.getUserName(id);
    }

    @Operation(summary = "获取用户信息")
    @GetMapping(value = "mb3/{id}")
    public User mb3(
            @PathVariable(value = "id") @Parameter(description = "path参数") long id
    ) {
        return userMapper.getUserInfo(id);
    }

    @Operation(summary = "获取用户列表")
    @GetMapping(value = "mb4/{id}")
    public List<User> mb4(
            @PathVariable(value = "id") @Parameter(description = "path参数") long id
    ) {
        return userMapper.getUserList(id);
    }

    @Operation(summary = "新增用户,并返回ID")
    @GetMapping(value = "mb5")
    public int mb5() {
        User user = new User();
        user.setEmail("litblc@xxx.com");
        user.setUuid("litblc-uuid");
        user.setMobile("13111111111");
        user.setPassword("xxx");
        user.setName("大修哥");

        userMapper.addUser(user);

        // 获取刚刚插入的user自增id
        return user.getId();
    }
}

更多资料参考 https://www.liaoxuefeng.com/wiki/1252599548343744/1331313418174498

使用xml方式使用mybatis

在application.yml中添加mapper-locations配置,指定待解析的mapper的xml文件

代码语言:javascript复制
mybatis:
  # 待解析的mapper的xml文件
  mapper-locations: classpath:/mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true
    default-fetch-size: 100
    default-statement-timeout: 30

在同级文件夹(也就是resources)下创建mapper文件夹,可以用别的名称,与上面配置的路径名称对应上即可。 创建一个UserMapper.xml文件(注意的是<!DOCTYPE mapper标签必须有,而且指定是mapper):

代码语言:javascript复制
<?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.example.springbootstudy.mapper.UserMapper">
    <select id="findByUUID" resultType="com.example.springbootstudy.dao.UserInfoDao">
        SELECT * FROM users WHERE uuid=#{uuid}
    </select>
    <select id="getAll" resultType="com.example.springbootstudy.dao.UserInfoDao">
        SELECT * FROM users order by id desc
    </select>
</mapper>

这时,你的UserMapper.java中定义这个findByUUID接口,就不需要使用注解定义sql了:

代码语言:javascript复制
@Mapper
public interface UserMapper {
    @Select(value = "select count(id) from users")
    int getAllCount();

    @Select("select name from users where id = #{id}")
    String getUserName(long id);

    // ...

    // 测试使用xml中配置sql语句
    UserInfoDao findByUUID(@Param("uuid") String uuid);

    // 返回map的话必须指定map的key用哪个字段
    // List<UserInfoDao> getAll();
    @MapKey("id")
    Map<Integer, UserInfoDao> getAll();
}

使用上是一样的:

代码语言:javascript复制
@Operation(summary = "测试使用mybatis的xml方式")
@GetMapping(value = "mb6/{id}")
public UserInfoDao mb6(
        @PathVariable(value = "id") @Parameter(description = "path参数") String uuid
) {
    System.out.println(uuid);
    return userMapper.findByUUID(String.valueOf(uuid));
}

@Operation(summary = "测试使用mybatis的xml方式-返回list/map")
@GetMapping(value = "mb7")
public Map<Integer, UserInfoDao> mb7() {
    return userMapper.getAll();
}

总结:MyBatis完全手写,每增加一个查询都需要先编写SQL并增加接口方法。

更多参考 https://blog.csdn.net/m0_37989980/article/details/105588234

mybatis-plus:更方便的使用mybatis

更多了解参考官网 https://baomidou.com/

mybatis-plus具有无侵入、损耗小、只做增强不做改变、强大的 CRUD 操作、丰富的查询构造器等特点,在国内十分火爆。

  • 引入依赖
代码语言:javascript复制
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.2</version>
</dependency>
  • 创建实体模型entity
代码语言:javascript复制
package com.example.springbootstudy.entity;

//import jakarta.persistence.Table;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

// `@TableName`是mp的注解,主要是实现实体类型和数据库中的表实现映射。
// 不要将`@TableName`和`@Table`注解认为是一个,虽然功能相同,但是,`@TableName`是`mybatis-plus`中的注解, `@Table`是`Hibernate`中的注解(JPA)

@Data
@TableName(value = "posts")
public class Poster {
    private Long id;
    private String uuid;
    private Long user_id;
    private String title;
    private String summary;
    private String poster;
    private String content;
}
  • 创建与实体entity对应的mapper接口:PostsMapper
代码语言:javascript复制
package com.example.springbootstudy.mapper;

import com.example.springbootstudy.entity.Poster;
import org.apache.ibatis.annotations.Mapper;

// `@Mapper`注解可以在每个mapper上定义,也可以在`Spring Boot`启动类中添加 `@MapperScan`直接扫描整个文件夹
@Mapper
public interface PostsMapper extends IBaseMapper<Poster> {
    // 添加复杂的操作数据库方法,就可以在xml中写,跟mybatis用法一样
}

上面继承的IBaseMapper是自定义的,继承自mybatis-plus的BaseMapper,提供了一些封装好的通用的curd操作,通过源码可以看到。

代码语言:javascript复制
package com.example.springbootstudy.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;

// mapper父类,注意这个类不要让 mp 扫描到!!
public interface IBaseMapper<T> extends BaseMapper<T> {
    // 这里可以放一些自定义的公共的方法
}
  • 控制器使用测试(一般是由service调用,这里偷懒了直接控制器写逻辑测试用)
代码语言:javascript复制
// 使用@RestController替代@Controller后,每个方法自动变成API接口方法。直接返回json。
// @Controller
@RestController
@RequestMapping("/six")
public class SixController {

    private final PostsMapper postsMapper;

    public SixController(PostsMapper postsMapper) {
        this.postsMapper = postsMapper;
    }

    @GetMapping(value = "f3")
    public List<Poster> func3() {
        List<Poster> list = postsMapper.selectList(null);
        return list;
    }
}
  • mybatis-plus进阶学习

插入一条数据自增ID, 并学习使用lombok的@Accessors(chain = true)注解

代码语言:javascript复制
@Data
// 链式访问,该注解设置chain=true,生成setter方法返回this(也就是返回的是对象),代替了默认的返回void
@Accessors(chain = true)
@TableName(value = "posts")
public class Poster {
    // 指定自增策略,这样insert的时候id才是自增的
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    private String uuid;

    // 比如没开启自动转驼峰的配置项map-underscore-to-camel-case:false,或者名字不一样可以自行指定
    @TableField(value = "user_id")
    private Long userId;

    private String title;
    private String summary;
    private String poster;
    private String content;

    // 自动维护创建、更新时间
    // 如何返回创建后的实体
}

添加mybatis-plus的配置,配置带解析的mapper的xml文件地址,方便进行自定义复杂sql查询

代码语言:javascript复制
mybatis:
  # 待解析的mapper的xml文件
  mapper-locations: classpath:/mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true
    default-fetch-size: 100
    default-statement-timeout: 30
    ...

mybatis-plus:
  # 待解析的mapper的xml文件,跟上面一样意思
  mapper-locations: classpath:/mapper/*.xml
  configuration:
    map-underscore-to-camel-case: false

在PostsMapper中添加一个自定义sql的方法,并在PostsMapper.xml中补全

代码语言:javascript复制
@Mapper
public interface PostsMapper extends IBaseMapper<Poster> {
    // 添加复杂的操作数据库方法,就可以在xml中写,跟mybatis用法一样
    List<UserInfoDao> getAll();
}

新建一个xml,主要注意namespace别写错喽,不然找不到

代码语言:javascript复制
<mapper namespace="com.example.springbootstudy.mapper.PostsMapper">
    <select id="getAll" resultType="com.example.springbootstudy.dao.UserInfoDao">
        SELECT * FROM users order by id desc
    </select>
</mapper>

控制器调用示例

代码语言:javascript复制
    @Operation(summary = "新增文章")
    @GetMapping(value = "f4")
    public Poster func4() {
        // 在entity上定义了`@Accessors(chain = true)`,就可以直接链式调用所有方法,因为setter方法返回当前对象了
        Poster poster = new Poster().setPoster("https://xxx/xxx.jpg");
        poster.setUuid("xxx-uuid-11");
        poster.setTitle("测试").setSummary("摘要").setUserId(10L).setContent("{"a":32}");

        postsMapper.insert(poster);  // 返回插入数量
        return poster;
    }

    @Operation(summary = "测试xml方式的sql查询")
    @GetMapping(value = "f6")
    public List<UserInfoDao> func6() {
        return this.postsMapper.getAll();
    }
  • JSON格式字段咋处理

当数据库存储json时候,建议字段仍然用textvarchar等字符串格式,entity对应也用String,保存入库的时候正常按照字符串类型操作; 查询的时候想返回JSON格式的话,可以再写个dto, 把字段改成JSONObject格式,然后手动赋值进去(JSONObject是使用com.alibabafastjson依赖)。

如果数据库字段格式是json,那entity也必须是json格式的了,比如:

  1. 引入json包
代码语言:javascript复制
        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.41</version>
        </dependency>
  1. entity中的jsonInfo字段是json格式
代码语言:javascript复制
@Data
@Accessors(chain = true)
@TableName(value = "reports", autoResultMap = true)
public class Report {
    @TableId(type = IdType.AUTO)
    private Long id;
    private Long userId;
    private Long resourceId;
    private String reason;
    private String type;

    // json字段要使用`com.alibaba.fastjson.JSONObject`的这个类型,可以不定义json具体字段格式,并且不会报错没序列化错误
    // 同时要设置`typeHandler`,这样获取的时候就是json,只不过存的时候需要同样生成该类型json入库
    @TableField(value = "json_info", typeHandler = JacksonTypeHandler.class)
    private JSONObject jsonInfo;
  1. 创建mapper接口,注入entity
代码语言:javascript复制
@Mapper
public interface ReportMapper extends IBaseMapper<Report>{

}
  1. 控制器读写
代码语言:javascript复制
package com.example.springbootstudy.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.example.springbootstudy.entity.Report;
import com.example.springbootstudy.mapper.ReportMapper;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/seven")
public class SevenController {
    private final ReportMapper reportMapper;

    public SevenController (ReportMapper reportMapper) {
        this.reportMapper = reportMapper;
    }

    @GetMapping(value = "/s1")
    public Report s1() {
        return this.reportMapper.selectById(1);
    }

    @Operation(summary = "测试json字段或者json格式的字符串读写问题")
    @GetMapping(value = "/s2")
    public Report s2() {
        
        // 使用同样的json格式化类型
        JSONObject jsonInfo = JSON.parseObject("{"a":3222}");

        Report report = new Report().setUserId(2L)
                .setResourceId(23L).setType("post")
                .setJsonInfo(jsonInfo)
                .setReason(String.valueOf(jsonInfo));
        this.reportMapper.insert(report);
        return report;
    }
}
  • 想自动更新某些字段,比如创建时间、更新时间、删除时间等

首先在entity字段中添加@TableField(fill = FieldFill.INSERT)

代码语言:javascript复制
    // 创建时自动填充
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createdAt;

    // 创建与修改时自动填充
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updatedAt;

添加了fill注解并不会字段维护,而是需要写个handle处理,比如写个组件类专门设置/component/MybatisPlusAutoFill.java

代码语言:javascript复制
package com.example.springbootstudy.component;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.example.springbootstudy.constant.MybatisPlusAutoFillProperties;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

/**
 * 自定义实现类
 * @Author zhenhuaixiu
 * @Date 2023/10/16 10:55
 * @Version 1.0
 */

@Component
public class MybatisPlusAutoFill implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        // String format = "yyyy-MM-dd HH:mm:ss";
        // String now = LocalDateTime.now().format(DateTimeFormatter.ofPattern(format));

        this.strictInsertFill(metaObject, MybatisPlusAutoFillProperties.CREATED_AT, LocalDateTime::now, LocalDateTime.class);
        this.strictInsertFill(metaObject, MybatisPlusAutoFillProperties.UPDATED_AT, LocalDateTime.class, LocalDateTime.now());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        System.out.println("updateFill");
        this.strictUpdateFill(metaObject, MybatisPlusAutoFillProperties.UPDATED_AT, LocalDateTime::now, LocalDateTime.class);
    }
}

上面的自动填充类调用了MybatisPlusAutoFillProperties,是我们写的所有需要自动更新的字段常量类,比如/constant/MybatisPlusAutoFillProperties.java

代码语言:javascript复制
package com.example.springbootstudy.constant;

public class MybatisPlusAutoFillProperties {
    // 字段要与entity对应上,咱们开启了自动转驼峰,所以是驼峰形势的
    public static final String CREATED_AT = "createdAt";
    public static final String UPDATED_AT = "updatedAt";
}

这样在我们再次调用insert或者update的时候,就能看到更新了。

mybatis-plus就写到这,其他的去官网看,特别详细 https://baomidou.com/

项目代码分层解读

更多参考 https://www.jianshu.com/p/18c4418e9b99

  1. Entity: 实体层,一个表对应一个,相当于php的model
  2. Dao:持久层,用来封装操作数据的方法,相当于php的repositories层,会调用entity。(使用mybatis的mapper层其实就相当于Dao层)
  3. Service:业务层,与php一致,调用dao层接口,接收dao层返回的数据,完成项目的基本功能设计
  4. Controller:控制层,与php一致,前后端交互,接受前端请求,调用service层并接收返回的数据
  5. 总体流程:Controller-->service接口-->serviceImpl-->dao接口-->daoImpl-->mapper-->db

Dao VS Mapper Dao通常被认为是传统的Java数据访问层,它将数据访问的逻辑封装在一组DAO接口和实现类中。这些接口和实现类主要用于将Java对象映射到数据库表,并执行一些数据操作,例如插入、更新、删除和查询。DAO通常使用JDBC和SQL语句来实现数据操作。在MyBatis中,DAO可以使用MyBatis的SqlSession和SqlSessionFactory来管理数据库连接和事务,并且可以使用MyBatis的动态SQL功能执行高度灵活的查询。 Mapper是MyBatis中的另一种数据访问层实现方式,它基于XML或注解的方式来描述SQL语句和参数映射,提供了更灵活、更简洁的数据访问方式。Mapper使用XML或注解来描述SQL语句和参数映射,并将它们映射到Java方法上。在执行数据操作时,Mapper会将Java方法转换为对应的SQL语句,并使用SqlSession执行该SQL语句。相对于Dao,Mapper更加灵活,并且在编写SQL语句时提供了更多的可读性和可维护性。 在实际开发中,选择使用Dao还是Mapper取决于具体的需求和个人偏好。如果您更喜欢面向接口的编程模型,并且需要使用Java代码来定义和执行数据操作,那么Dao可能是一个更好的选择。如果您需要更灵活、更简洁的方式来描述SQL语句,并且不介意使用XML或注解来描述它们,那么Mapper可能更适合您的需求。 这里要注意区分Dto,Dto是数据传输对象,类似response返回类,用来封装返回对象格式

Bean的生命周期理解

Spring Cloud

概念理解

  1. Spring:是JavaEE的一个轻量级开发框架,主营IoC和AOP,集成JDBC、ORM、MVC等功能便于开发
  2. Spring Boot:是基于Spring,提供开箱即用的积木式组件,目的是提升开发效率
  3. Spring Cloud:分布式应用程序,为了让分布式应用程序编写更方便,更容易而提供的一组基础设施,它的核心是Spring框架,利用Spring Boot的自动配置,力图实现最简化的分布式应用程序开发

初始化创建一个spring cloud微服务项目

  1. 首先通过IDEA创建一个maven项目,比如命名为scd(spring cloud demo),然后一般会删除src目录,顶级目录不做编程。
代码语言:javascript复制
文件-新建-项目-Maven-下一步
  1. 在刚刚创建的项目右键新建一个模块,都选择maven项目,然后选择父项scd,创建。 IDEA会自动在顶级scd的pom文件中生成模块,并标记packaging为pom类型。这里我们先创建三个模块,分别是parent、config、common。

<packaging>pom</packaging>的理解

  1. 记得设置文件编码全都改成UTF-8 在setting - File Encodings中设置,全局的、项目的、属性文件的都改成UTF-8.
代码语言:javascript复制
  #QR{padding-top:20px;} #QR a{border:0} #QR img{width:180px;max-width:100%;display:inline-block;margin:.8em 2em 0 2em} #rewardButton {     border: 1px solid #ccc;     /*width: 20%;*/     line-height: 53px;     text-align: center;     height: 70px;     display: block;     border-radius: 10px;     -webkit-transition-duration: .4s;     transition-duration: .4s;     background-color: #f77b83;     color: #f7f7f7;     margin: 20px auto;     padding: 8px 25px; } #rewardButton:hover {     color: #eb5055;     border-color: #f77b83;     outline-style: none;     background-color: floralwhite; 	}  

0 人点赞