首页> 标签> mybatis
"mybatis"
共 6245 条结果
全部 问答 文章 公开课 课程 电子书 技术圈 体验
大公司为什么禁止SpringBoot项目使用Tomcat?
本文已经收录到Github仓库,该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点,欢迎star~Github地址:https://github.com/Tyson0314/Java-learning前言在SpringBoot框架中,我们使用最多的是Tomcat,这是SpringBoot默认的容器技术,而且是内嵌式的Tomcat。同时,SpringBoot也支持Undertow容器,我们可以很方便的用Undertow替换Tomcat,而Undertow的性能和内存使用方面都优于Tomcat,那我们如何使用Undertow技术呢?本文将为大家细细讲解。SpringBoot中的Tomcat容器SpringBoot可以说是目前最火的Java Web框架了。它将开发者从繁重的xml解救了出来,让开发者在几分钟内就可以创建一个完整的Web服务,极大的提高了开发者的工作效率。Web容器技术是Web项目必不可少的组成部分,因为任Web项目都要借助容器技术来运行起来。在SpringBoot框架中,我们使用最多的是Tomcat,这是SpringBoot默认的容器技术,而且是内嵌式的Tomcat。推荐:几乎涵盖你需要的SpringBoot所有操作。SpringBoot设置Undertow对于Tomcat技术,Java程序员应该都非常熟悉,它是Web应用最常用的容器技术。我们最早的开发的项目基本都是部署在Tomcat下运行,那除了Tomcat容器,SpringBoot中我们还可以使用什么容器技术呢?没错,就是题目中的Undertow容器技术。SrpingBoot已经完全继承了Undertow技术,我们只需要引入Undertow的依赖即可,如下图所示。配置好以后,我们启动应用程序,发现容器已经替换为Undertow。那我们为什么需要替换Tomcat为Undertow技术呢?Tomcat与Undertow的优劣对比Tomcat是Apache基金下的一个轻量级的Servlet容器,支持Servlet和JSP。Tomcat具有Web服务器特有的功能,包括 Tomcat管理和控制平台、安全局管理和Tomcat阀等。Tomcat本身包含了HTTP服务器,因此也可以视作单独的Web服务器。但是,Tomcat和ApacheHTTP服务器不是一个东西,ApacheHTTP服务器是用C语言实现的HTTP Web服务器。Tomcat是完全免费的,深受开发者的喜爱。Undertow是Red Hat公司的开源产品, 它完全采用Java语言开发,是一款灵活的高性能Web服务器,支持阻塞IO和非阻塞IO。由于Undertow采用Java语言开发,可以直接嵌入到Java项目中使用。同时, Undertow完全支持Servlet和Web Socket,在高并发情况下表现非常出色。我们在相同机器配置下压测Tomcat和Undertow,得到的测试结果如下所示:QPS测试结果对比: TomcatUndertow内存使用对比:TomcatUndertow通过测试发现,在高并发系统中,Tomcat相对来说比较弱。在相同的机器配置下,模拟相等的请求数,Undertow在性能和内存使用方面都是最优的。并且Undertow新版本默认使用持久连接,这将会进一步提高它的并发吞吐能力。所以,如果是高并发的业务系统,Undertow是最佳选择。最后SpingBoot中我们既可以使用Tomcat作为Http服务,也可以用Undertow来代替。Undertow在高并发业务场景中,性能优于Tomcat。所以,如果我们的系统是高并发请求,不妨使用一下Undertow,你会发现你的系统性能会得到很大的提升。参考链接:原文地址:toutiao.com/a677547665941699021最后给大家分享一个Github仓库,上面有大彬整理的300多本经典的计算机书籍PDF,包括C语言、C++、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生等,可以star一下,下次找书直接在上面搜索,仓库持续更新中~Github地址:https://github.com/Tyson0314/java-books
文章
Java  ·  应用服务中间件  ·  程序员  ·  数据库  ·  C语言  ·  开发者  ·  C++  ·  Python  ·  容器  ·  mybatis
2023-01-28
类是如何加载的?
在 Java 中,类加载的流程有一个专门的机制叫做“类加载机制”。类加载机制是指一个类在 Java 虚拟机(JVM)中的执行流程,它也是 Java 程序能够正常执行的关键所在,那它的具体执行流程是啥?接下来我们一起来看。流程概述在 JVM 中,类加载会经历以下 5 个阶段:加载阶段(Loading)验证阶段(Verification)准备阶段(Preparation)解析阶段(Resolution)初始化阶段(Initialization)其中:验证阶段、准备阶段和解析阶段合起来又称为连接阶段,所以以上 5 个阶段又可以划分为 3 大类:加载阶段(Loading)连接阶段(Linking)验证阶段(Verification)准备阶段(Preparation)解析阶段(Resolution)初始化阶段(Initialization)具体分类如下图所示:这 3 大类、5 个流程的具体执行细节是这样的。1.加载阶段简单来说,加载阶段就是将类文件加载到内存中的过程。在加载阶段,JVM 需要完成以下 3 件事:通过一个类的全限定名来获取定义此类的二进制字节流;将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。## 2.连接阶段连接阶段又分为:验证阶段(Verification)、准备阶段(Preparation)和解析阶段(Resolution),具体执行的细节如下。### 2.1 验证阶段验证阶段也叫做校验阶段,它主要是用来验证加载到内存中的类是否是安全合规的文件,验证的主要动作大概有以下几个(当然,以下细节如果实在记不住也没关系):文件格式校验包括常量池中的常量类型、Class 文件的各个部分是否被删除或被追加了其他信息等;元数据校验包括父类正确性校验(检查父类是否有被 final 修饰)、抽象类校验等;字节码校验,此步骤最为关键和复杂,主要用于校验程序中的语义是否合法且符合逻辑;符号引用校验,对类自身以外比如常量池中的各种符号引用的信息进行匹配性校验。2.2 准备阶段准备阶段就开始给类中的静态变量设置默认值了,注意这里不是给静态变量设置初始值,而是设置默认值,二者还是有很大区别的。举个例子,比如代码中写的内容是:public static int number = 10;那么此时是给 number 变量设置的 int 值是默认值 0,而非初始值 10。2.3 解析阶段解析阶段就是将常量池中的符号引用更换成直接引用了,所谓的符号引用是指以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可;而直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。符号引用和直接引用有一个重要的区别:使用符号引用时被引用的目标不一定已经加载到内存中;而使用直接引用时,引用的目标必定已经存在虚拟机的内存中了。3.初始化阶段初始化阶段,Java 虚拟机真正开始执行类中编写的 Java 程序代码,将主导权移交给应用程序。到这一步骤之后,类的加载过程就算正式完成了,此时会给静态变量设置初始值,并执行静态代码块的内容。总结类加载流程总共分为 3 大类,5 个主要流程:加载阶段(Loading):将类文件加载到内存。连接阶段(Linking)验证阶段(Verification):类文件安全性效验。准备阶段(Preparation):给静态变量设置默认值。解析阶段(Resolution):将符号引用转换为直接引用。初始化阶段(Initialization):执行静态代码块和给静态变量设置初始值。本文已收录到 Gitee 开源仓库《Java 面试指南》,其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。Java 面试有它就够了,点击查看详情:interview: 400+ 道 Java 常见面试题和解析,持续更新中......
文章
消息中间件  ·  存储  ·  安全  ·  前端开发  ·  Java  ·  关系型数据库  ·  MySQL  ·  数据库连接  ·  Spring  ·  mybatis
2023-01-28
Spring Boot 数据操作组件Spring Data JPA
 Spring Boot 数据操作组件Spring Data JPA如果觉得写的还可以,点个赞支持一下笔者呗!你的点赞和关注会让我更快更新哦。笔者会持续更新关于Java和大数据有关的文章。目前集中精力在更新java框架的内容。1. 历史发展持久化操作(对数据库的操作)一直都是 Java 的核心内容,在 Java 的发展历史中,数据库持久化层面的框架也在不断地发展与更新。JDBC( Java DataBase Connectivity )是 Java 中访问数据库的规范,由 SUN 公司制定(目前 SUN 已经被 Oracle 收购)。原生的 JDBC 代码臃肿、冗余,非常难用。这一度使得 Java EE 在当时备受质疑。所以 SUN 公司推出了大名鼎鼎的 EJB,现在已经很少人有提及 EJB 了,当年曾经大名鼎鼎也是因为是 SUN 公司的产品(连技术圈也开始拼爹了)。但由于 EJB 太重量级,太难用,很快就被当时的新晋小生 Hibernate 所取代(事实再一次告诉我们,爹再牛逼也只能帮你一时,关键还得看自己)。Hibernate 凭借自身强大的功能迅速走红,与 Struts 和 Spring 组成了当时风靡一时的 SSH 三人组。后来 SUN 公司借鉴了 Hibernate 的设计思路,制定了 JPA( Java Persistence API )规范。在 Hibernate 后来的版本中,也实现了对于 JPA 的完全支持。这也使 HIbernate 在当时进一步巩固了自己在持久层框架的霸主地位。随着互联网的发展,尤其是移动互联网的飞速扩展,HIbernate 对于性能和灵活性的需求显得捉襟见肘,已经无法满足日新月异的互联网业务场景了。这个时候,又一个年轻人站了出来,它就是 Mybatis。Mybatis 凭借着其简单、高效、灵活等特点迅速成为了新时代的宠儿。2. 两种思路数据库持久层框架通常可以分为两种类型,两者的关注对象不同。其中一种关注的重心是数据库(表),对 JDBC 做一定的封装,将 JDBC 冗余的样板代码 “隐藏” 起来,使其变得方便和快捷,此中的代表就是 Mybatis;另一种的关注重心则是 Java Entity。通过实体类来映射表之间的关系,HIbernate 就是这一分类中的翘楚( Spring Data JPA 就是基于 HIbernate 实现的)。Spring Data JPA 对 JPA( Java Persistence API )进行了进一步的封装,使得对数据库的常用操作变得异常简单。到底能有多简单呢?简单到令人无法相信,不信?那我们走着瞧!3. 动手3.1 准备在使用 Spring Data JPA 之前,我们需要做一些准备工作。因为 Spring Data JPA 是持久层的组件,那么我们肯定会就要用到数据库了。所以,我们需要先安装数据库,不用说你也想到了,我们使用的数据库是 MySQL(如何安装 MySQL 就不用我多说了吧)。MySQL 安装完成后,我们需要创建一个数据库,在本专栏里,数据库的名字叫做 springboot,当然你也可以选择你喜欢的名字。3.2 添加依赖要使用 Spring Data JPA 我们需要在 pom 文件中添加如下依赖:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>3.3 添加配置添加完依赖,我们需要增加一些配置,才能让我们的工程访问到数据库。我们需要在 application.yml 文件中添加如下配置:#数据源配置 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/springboot?characterEncoding=utf8&useSSL=false driver-class-name: com.mysql.jdbc.Driver username: root password: 123456 jpa: hibernate: ddl-auto: update show-sql: true database-platform: org.hibernate.dialect.MySQL5InnoDBDialect配置介绍:url 数据库地址与参数driver-class-name 连接数据库的驱动username 数据库用户名password 数据库密码上面四项配置比较简单,稍微有点编程经验的一看就能明白是什么,这里就不过多介绍了。我们简单说一下 jpa 下面的几项配置:show-sql 很简单,设为 true 代表打印 sql,false 则为不打印;database-platform 也不难,用来指定使用哪种 MySQL 的存储引擎,我们这里使用的是 InnoDB。ddl-auto 这个配置简单说一下,它有四个值可选,具体含义如下:create 每次启动重新创建表(启动过程中原来的数据会被清除)create-drop 每次启动重新创建表,并在程序结束是删除表,如果表不存在会报错update 每次启动如表结构不一致,则更新表结构,原数居会保留(一般使用该项)validate 每次启动验证表结构,如果不一致则报错3.4 创建实体我们可以在之前创建的 User 类中加入如下注解,使其变成我们的实体类:@Data @Entity public class User { @Id @GeneratedValue private Integer id; private String name; ....... }注解介绍:在 User 类中,我们是用了三个新注解——@Entity、@Id 和 @GeneratedValue。@Entity 用来声明 User 是一个实体类,将会与数据库里的 user 表对应起来;@Id 和 @GeneratedValue 则标识 user 的 id 属性为 user 表的自增 Id。如果想要自定义表名,比如想要让与 User 类对应的表叫做 t_user 的话,那么只需要使用 @Table(name = “t_user”) 即可。同样想要自定义列名也非常简单,比如想要将 name 与表里的 user_name 对应,只需要在 name 属性上加上 @Column(name = “user_name”) 即可。3.5 生成表结构接下来我们可以让 Spring Data JPA 根据实体类来生成表结构了,启动程序,会在控制台看到如下日志:Hibernate: create table user (id integer not null, age integer not null, birth_day date, email varchar(255), name varchar(255) not null, primary key (id)) engine=InnoDB看到上面的日志输出,说明 user 表被创建了,我们可以登录 MySQL 去验证一下:我们看到,user 表已经被成功创建。我们再检查一下 user 表的结构是否符合我们的预期,使用 desc user 命令查看,结果如下:OK,user 表的结构跟我们的 User 实体类一致。3.6 完善剩余代码创建 User 类的持久化接口 UserRepository:public interface UserRepository extends JpaRepository<User,Integer> { }UserRepository 中不需要写任何代码,但依然可以帮我们完成增删改查的操作,下面我们继续。创建 UserController 并实现增删改查四个方法:@Api @RestController @RequestMapping("/users") public class UserController { @Autowired private UserRepository userRepository; @ApiOperation(value = "根据id获取用户信息") @GetMapping("/{id}") public User get(@PathVariable int id) { return userRepository.findById(id).get(); } @ApiOperation(value = "创建用户") @PostMapping("") public User create(@RequestBody User user) { return userRepository.save(user); } @ApiOperation(value = "更新用户") @PutMapping("") public User update(@RequestBody User user) { return userRepository.save(user); } @ApiOperation(value = "删除用户") @DeleteMapping("/{id}") public void delete(@PathVariable Integer id) { userRepository.deleteById(id); } }3.7 效果接下来我们启动程序,打开 Swagger 去验证一下这些接口是否好用。首先,我们调用 create 方法来创建一个 user。参数如下:{ "age": 18, "birthDay": "2011-01-01", "email": "xiaoming@imooc.com", "name": "小明" }执行完成后,登录 MySQL 查看,结果如下:我们通过 Controller 中的 create 方法 调用了 UserRepository 中的 save 方法成功插入了一条用户信息。其他方法的效果就不在这里一一演示了,留给你自己去把玩吧。4. 扩展4.1 基于方法名查询假如我想要个性化查询怎么办呢?比如我想按照年龄来查询用户,其实也非常简单,只需要按照 Spring Data JPA 的规范在 UserRepository 中定义相应的接口即可。没错,就是只定义个接口,不需要写实现!public interface UserRepository extends JpaRepository<User,Integer> { public List<User> findByAge(Integer age); }在 UserRepository 中加入上面一句代码就完成了根据年龄查询用户的功能。当然,我们需要在 Controller 里面调用一下:@ApiOperation(value = "根据年龄查询用户") @GetMapping("/age/{age}") public List<User> getByAge(@PathVariable Integer age) { return userRepository.findByAge(age); }OK,这样就搞定了,可以去 Swagger 上测试一下了,效果就不在这里展示了。更多关键字规则对照表:关键字例子JPQL 片段AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2Is,EqualsfindByFirstname,findByFirstnameIs,findByFirstnameEquals… where x.firstname = ?1BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2LessThanfindByAgeLessThan… where x.age < ?1LessThanEqualfindByAgeLessThanEqual… where x.age <= ?1GreaterThanfindByAgeGreaterThan… where x.age > ?1GreaterThanEqualfindByAgeGreaterThanEqual… where x.age >= ?1AfterfindByStartDateAfter… where x.startDate > ?1BeforefindByStartDateBefore… where x.startDate < ?1IsNullfindByAgeIsNull… where x.age is nullIsNotNull,NotNullfindByAge(Is)NotNull… where x.age not nullLikefindByFirstnameLike… where x.firstname like ?1NotLikefindByFirstnameNotLike… where x.firstname not like ?1StartingWithfindByFirstnameStartingWith… where x.firstname like ?1 (parameter bound with appended %)EndingWithfindByFirstnameEndingWith… where x.firstname like ?1 (parameter bound with prepended %)ContainingfindByFirstnameContaining… where x.firstname like ?1 (parameter bound wrapped in %)OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname descNotfindByLastnameNot… where x.lastname <> ?1InfindByAgeIn(Collection ages)… where x.age in ?1NotInfindByAgeNotIn(Collection ages)… where x.age not in ?1TruefindByActiveTrue()… where x.active = trueFalsefindByActiveFalse()… where x.active = falseIgnoreCasefindByFirstnameIgnoreCase… where UPPER(x.firstame) = UPPER(?1)4.2 分页、排序那么想要分页、排序该怎么办呢?支持吗?方便吗?必须的!而且比上面的还简单,UserRepository 中不需要添加任何内容,直接在 Controller 里调用即可:@ApiOperation(value = "分页获取用户列表") @GetMapping("") public Page<User> list(String property, String direction, Integer page, Integer size) { Pageable pageable = PageRequest.of(page, size, Sort.Direction.fromString(direction),property); return userRepository.findAll(pageable); }PS:页码从 0 开始哦打完收工!就是如此简单,两行代码解决战斗!嗯,这操作很 Spring Boot!Spring Boot 就是想让你只专注于你想专注的事情,其他脏活累活它来帮你完成。其实这些都是基于一个理念——约定由于配置。5. 总结这一小节我们学习了 Spring Data JPA 的相关内容,对其特点与用法有了比较全面的认识。并且通过几个小例子进一步巩固了对它的理解。这里只是对 Spring Data JPA 进行了一些初步的学习,其实它还有很多很强大的功能,比如我们可以使用 JPQL 来执行自定义操作,还可以使用原生 SQL 去开发我们想要的功能。这些功能可以通过 @Query 注解来实现,另外还可以像 Mybatis 那样基于 Example 进行查询,或者使用 JPA Query 等高级特性。更多有意思的功能等待你去发现。
文章
SQL  ·  Oracle  ·  Java  ·  关系型数据库  ·  MySQL  ·  数据库连接  ·  API  ·  数据库  ·  Spring  ·  mybatis
2023-01-25
Mybatis详解(2)
Mybatis详解(2)如果觉得写的还可以,点个赞支持一下笔者呗!你的点赞和关注会让我更快更新哦。笔者会持续更新关于Java和大数据有关的文章。目前集中精力在更新java框架的内容。1.1 整合 MyBatis我们想要使用 MyBatis 首先需要添加对 MyBatis 的依赖。Spring Boot 向来以简单快速的特点行走江湖,在整合 MyBatis 的时候也依然遵循着这一原则,只需引入 MyBatis 相关的 starter 即可,具体如下:添加依赖<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.48</version> </dependency>当然,我们使用的数据库为 MySQL ,我们也需要引入 MySQL 的相关依赖(在 JPA 那一小节就已经接触过了)。数据库配置接下来,我们需要配置一下数据库的相关信息。在 application.yml 文件中添加如下配置:#数据源配置 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/springboot?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai driver-class-name: com.mysql.jdbc.Driver username: root password: 123456配置项:url:连接数据库的地址driver-class-name:连接数据库的驱动username:数据库的用户名password:数据库的密码简单介绍一下 url 中的几个参数:characterEncoding:字符集,指的是程序与 MySQL 交互时所使用的字符集,我们使用 utf8useSSL:用来指定是否启用 SSL ,我们这里设置为不启用serverTimezone:时区,如果不指定,在使用 6.x 以后版本的驱动时会出现一些问题,我们设置为 Asia/Shanghai交给国家(Spring)我们知道,MyBatis 是用 Mapper 接口将我们的程序与一系列持久化操作串联起来的。对 Spring 有了解的同学都知道,如果想要把工作交给 Spring 来进行统一管理,那么就需要让 Spring 知道它的存在,也就是让 Spring 扫描到它。因此,我们需要在我们的项目的主类中加上一个注解 —— @MapperScan ,用来扫描 MyBatis 的 Mapper。括号中填入 Mapper 所在的包路径,具体如下:.... @MapperScan("com.imooc.springboot.mybatis.mapper") public class SpringbootApplication { .... }PS:如果我们不使用 @MapperScan 这个注解,那么就需要在每个 Mapper 接口上加上 @Mapper,这样显然比较麻烦。1.2 使用 MyBatis我们了解到 JPA 是以 Java Entity 为原点,通过实体类来映射表之间的关系(可以通过 Java 实体类来生成对应的表结构和表关系);而 MyBatis 则是以数据库(表)为原点,它跟 JPA 是一种互逆的关系,这样看来 MyBatis 应该具备通过数据库(表)生成实体类的能力。没错,MyBatis 为我们提供了一个很好用的工具 —— MyBatis Generator,简称 MBG(总觉得这个缩写怪怪的)。创建表结构我们可以通过下面的建表语句来创建我们的 User 表,当然你也可以直接将 JPA 那一节根据实体类生成的 User 表直接拿来用。CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', `name` varchar(255) DEFAULT NULL COMMENT '姓名', `age` int(11) DEFAULT NULL COMMENT '年龄', `birth_day` timestamp NULL DEFAULT NULL COMMENT '生日', `email` varchar(255) DEFAULT NULL COMMENT '邮件', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户信息';配置 MBG有了表,我们还需要对 MBG 进行一下简单的配置。MBG 有多种使用方式 —— 命令行、Ant、Java、Eclipse plugin 和 Maven plugin。我们选择 Maven plugin 的方式,因为简单且直观。首先我们在 pom.xml 文件中添加对于 MBG 的依赖,将如下代码放到 标签下即可。<plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.7</version> <configuration> <configurationFile> ${basedir}/src/main/resources/generatorConfig.xml </configurationFile> <overwrite>true</overwrite> <verbose>true</verbose> </configuration> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.48</version> </dependency> </dependencies> </plugin>然后,我们在 src/main/resources 目录下创建一个名为 generatorConfig.xml 的配置文件,在该文件中填写相应的配置即可(代码就不帖出来了,可以到源码中去看,里面有详细的注释)。到此,MBG 就配置完成了,接下来我们就可以根据我们刚刚创建的 User 表来生成我们的实体以及 dao 层的方法了。在 IDEA 的右侧有一个 Maven Projects 的选项卡(如果没有,可以在菜单中的 view ==》Tool Windows 中找到),点击会弹出如下视图:找到 mybatis-generator 展开后,双击 mybatis-generator:generate 运行即可。看到类似如下信息,则代表成功生成了我们的实体以及 dao 。[INFO] --- mybatis-generator-maven-plugin:1.3.7:generate (default-cli) @ mybatis --- [INFO] Connecting to the Database [INFO] Introspecting table user [INFO] Generating Example class for table user [INFO] Generating Record class for table user [INFO] Generating Mapper Interface for table user [INFO] Generating SQL Map for table user [INFO] Saving file UserMapper.xml [INFO] Saving file UserExample.java [INFO] Saving file User.java [INFO] Saving file UserMapper.java运行完成以后,可以在 Project 视图下看到生成的文件 ——UserMapper、User、UserExample 和 UserMapper.xml打开 UserMapper 接口,可以看到 MBG 帮我们生成了我们常用的增删改查以及统计接口:接口对应的实现在 UserMapper.xml 中,代码就不贴了,可以去源码中查看。下面我们就可以试一试 MBG 帮我们生成的这些代码好不好用了,我们直接在 Controller 层调用 Mapper 接口(实际开发时需要借助 Service),实现一下基本的 CRUD 方法:@RestController @RequestMapping("/users") public class UserController { @Autowired private UserMapper userMapper; @GetMapping("/{id}") public User get(@PathVariable int id) { return userMapper.selectByPrimaryKey(id); } @PostMapping public int create(@RequestBody User user) { return userMapper.insert(user); } @PutMapping public int update(@RequestBody User user) { return userMapper.updateByPrimaryKey(user); } @DeleteMapping("/{id}") public void delete(@PathVariable Integer id) { userMapper.deleteByPrimaryKey(id); } }写完代码,我们启动工程,然后可以去 Swagger 上面测试一下。2. 动态 SQL有时候,静态的 SQL 语句并不能满足应用程序的需求。我们可以根据一些条件,来动态地构建 SQL 语句。例如,在 Web 应用程序中, 比如有一些查询功能,需要根据一个或多个条件查询数据,然后根据具体传来的查询参数动态生成查询的 SQL 去数据库取数据。在实现这种类型的查询功能时,静态 SQL 就无法满足了。我们需要根据参数来构建动态的 SQL 语句,将用户提供的参数添加到 SQL 语句的 WHERE 子句中。动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC ,那么你很可能已经体会过拼接 SQL 所带来的痛苦了。经常会遇到少了空格,或者多了逗号之类的语法错误。MyBatis 的动态 SQL 将为我们彻底解决这类问题。MyBatis 采用功能强大的基于 OGNL 的表达式来实现动态 SQL。下面介绍几个我们日常开发中经常会用到的标签:ifchoose (when, otherwise)where (trim, set)foreach2.1 ifif 标签很简单,用法跟 Java 中的 if 一样,条件为 true 则执行,为 false 则跳过,看一个小例子:<select id="findUser" resultType="User"> SELECT * FROM USER WHERE 1=1 <if test="name != null"> AND name like #{name} </if> <if test="email != null"> AND email = #{email} </if> </select>2.2 choosechoose 标签类似 Java 中的 switch…case…default 语法,if 是二选一,而 choose 则是多选一,如果条件都不符合则选择最后 otherwise。具体见下面的代码:<select id="findUserByIdOrName" resultType="User"> SELECT * FROM USER WHERE 1=1 <choose> <when test="id != null"> AND id = #{id} </when> <when test="name != null"> AND name like #{name} </when> <otherwise> AND 0 = 1 </otherwise> </choose> </select>2.3 where细心的你可能已经发现了,在上面的例子中,where 后面都有一个 1=1 的条件,这种写法在以前我们手动拼接 SQL 的时候经常用(其实是一种无奈之举),MyBatis 中的 where 标签替我们消除了这一问题。只有当 where 中至少有一个条件符合时,MyBatis 才会为我们拼接 where 子句,并且会帮我们妥善的处理开头和结尾,如下面的例子,如果第一个条件不成立,拼接后的结果为:SELECT * FROM USER WHERE email= ?。<select id="findUser" resultType="User"> SELECT * FROM USER <where> <if test="name != null"> name like #{name} </if> <if test="email != null"> AND email = #{email} </if> </where> </select>2.4 foreach看到 foreach 这个标签,我想聪明的你不用我说也想到了,它就相当于 Java 中的 for。一点没错!下面通过一个根据 id 集合查询的例子吧:<select id="findUser" resultType="User"> SELECT * FROM USER <where> id in <foreach open="(" separator="," close=")" collection="list" item="id"> #{id} </foreach> </where> </select>3. 小结OK,本小节我们一起学习了 MyBatis 的使用。包括与 Spring Boot 整合,代码生成插件 MBG 的配置及使用,还通过几个实例学习了 MyBatis 的一个强大功能 —— 动态 SQL。MyBatis 动态 SQL 的使用是非常重要的,日常开发中,我们经常会用到。
文章
SQL  ·  Java  ·  关系型数据库  ·  MySQL  ·  数据库连接  ·  数据库  ·  Maven  ·  Windows  ·  mybatis  ·  Spring
2023-01-25
Mybatis详解(1)
Mybatis详解(1)如果觉得写的还可以,点个赞支持一下笔者呗!你的点赞和关注会让我更快更新哦。笔者会持续更新关于Java和大数据有关的文章。目前集中精力在更新java框架的内容。What is Mybatis提起 Mybatis 我们就会想到那只吉祥物—— 一只 Cosplay 忍者神龟的愤怒滴小鸟,想要了解一个技术最好的办法就是去它的官网看看,那么我们就先看一下官网是怎么向我们介绍 Mybatis 的。MyBatis is a first class persistence framework with support for custom SQL, stored procedures and advanced mappings. MyBatis eliminates almost all of the JDBC code and manual setting of parameters and retrieval of results. MyBatis can use simple XML or Annotations for configuration and map primitives, Map interfaces and Java POJOs (Plain Old Java Objects) to database records.上面是 Mybatis 官网对于 Mybatis 的一个介绍,简单翻译一下:MyBatis 是一个很牛逼的持久层框架,支持定制化 SQL、存储过程和高级映射。MyBatis 干掉了几乎所有的 JDBC 代码,不需要手动拼接参数和检索结果。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。2. 身世MyBatis 的前世为 iBatis( 该名称为 internet 和 abatis 的组合,寓意为互联网而生),iBatis 是由 Clinton Begin 在 2001 年发起的开源项目。在 2004 年,iBatis 发布了 2.0 版本,随后 Clinton Begin 将 iBatis 献给 Apache 软件基金会( Apache Software Foundation, ASF)。在之后的 6 年中,iBatis 在方法论、源码管理、社交、开源基础建设等方面都取得了很大的进步。2010 年 5 月 21 日,iBatis 项目迁移到 Google Code ,并更名为 MyBatis,正式投胎转世。版本从 3.0.1 一直更新到 3.2.3,稳定性得到很大提升。2010 年 6 月 16 日, iBatis 项目被正式归入 Apache Attic, 属性变为“只读”, 这意味着该项目在 iBatis 时代正式结束。2013 年 11 月 10 日,为了让更多的人参与到项目中,MyBatis 项目被迁移至 GitHub,而后一直发展至今,目前最新的稳定版本为 3.5.4 。3. 特点说 Mybatis 是目前国内 Java 持久层最为主流的技术,应该不会有什么人反对吧。它比 JPA(HIbernate)更加简单易用,学习门槛更低。遥想当年,在软件开发以企业管理系统(ERP、CRM 等)为主的年代,Hibernate 的模型化有助于系统分析与建模,顺应当时的需要,可谓叱咤风云,一时无两。然而世事无常,三十年河东三十年河西,如今已是移动互联网时代,业务重点从原来的模型、关系变成了大数据量、并发、高性能。这样一来,老牌 ORM 框架显得捉襟见肘了,新晋小生 Mybatis 走入我们的视野。从 Mybatis 的前世 iBatis 的名字( internet + abatis)来看,Mybatis 天生就具有互联网基因。互联网业务的特性就是快(更新迭代飞快)和灵活(各种花样各种玩法层出不穷)。既然我们说 Mybatis 天生具有互联网的基因,那么它必然也具备相应的特性,否则根本无法应对瞬息万变的互联网。与其他的 ORM (对象关系映射,比如老大哥 Hibernate)框架不同,Mybatis 没有延续以往 ORM 框架将 Java 对象与数据库表关联起来的思路,而是另辟蹊径将 Java 方法与 SQL 语句进行关联。在 JDBC 和 JPA (Hibernate)之间找到了一个平衡点,即解决了 JDBC 的繁琐冗余与低效的开发速率,又解决了 JPA (Hibernate)的“笨重”与不灵活。Mybatis 允许用户充分的利用数据库的各种功能,如存储过程、视图、复杂查询以及某种数据库独有的特性,又对 JDBC 做了一定的封装,加上约定优于配置的设计理念,让开发效率也有了质的提升。上面我们对 Mybatis 的特点进行了感性或者说是相对抽象的描述,那么下面我们换个角度用理性且具体的方式列出 Mybatis 的特点:它消除了大量的 JDBC 冗余代码它的学习门槛很低它能很好地与传统数据库协同工作它可以接受 SQL 语句它提供了与 Spring 框架的集成支持它提供了与第三方缓存类库的集成支持它引入了更好的性能4. 架构设计接下来我们从宏观层面了解一下 Mybatis 的整体设计与架构,先来看看 Mybatis 内部都有哪些组件。4.1 组件MyBatis 通过 mybatis-3 的应用程序接口和 ibatis-spring 向用户提供 SQL 访问方法, 而 ibatis-spring 底层仍依赖 mybatis-3 和 spring-tx 来实现对 SQL Mapping 和事务的支持。 mybatis-3 实现了 SQL 映射的全部功能, 通过构建器构建配置环境和 JDBC 环境, 对应用程序提供接口并使用执行器执行 SQL,4.2 功能架构Mybatis 按照功能划分,可以分为三部分:接口层:提供给外部使用的接口,开发人员通过这些本地接口来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。数据处理层:负责具体的 SQL 查找、SQL 解析、SQL 执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。基础支持层:负责最基础的功能支持,包括连接管理、事务管理、配置加载和缓存处理,这些都是公用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支持。打个比方,接口层就好比饭店的菜单,数据处理层就是厨房,基础支持层就好比是水、电、燃气之类的。客户按菜单点菜(调用接口),厨房负责做菜(数据处理),基础设施提供最基本的水、电、燃气的支持(基础支持)。4.3 包结构org └─ apache └─ ibatis +─ annotations +─ binding +─ builder +─ cache +─ cursor +─ datasource +─ exceptions +─ executor +─ io +─ javassist +─ jdbc +─ logging +─ mapping +─ ognl +─ parsing +─ plugin +─ reflection +─ scripting +─ session +─ transaction +─ typeMyBatis主要的源码包及其作用如下。annotations:注解包,包含所有mapper 接口中用到的注解binding:负责将 mapper 接口与 SQL 语句进行绑定builder:包含 Configuration 对象所有构建器,主要包括 XML、注解两种方式配置解析cache:缓存功能实现、包含各种缓存装饰器cursor:实现游标的方式查询数据、游标非常适合处理百万级别的数据查询,通常情况下不适合一次性加载到内存中这种方式类似使用 SAX 解析 XML 文件datasource: 数据源包括 jndi 数据源、连接池功能exceptions:框架异常包executor:包含 SQL 语句执行器,核心功能包io:资源文件读取javassist:Java 功能增强,包含类加载、编译、代码生成等jdbc: 测试代码lang:Java 版本的注解代码。 这个包只有2个注解 @UsesJava7 @UsesJava8 使用这个两个注解标识哪些可以使用 JDK7 API 哪些可以使用 JDK8 APIlogging: 日志功能代码,实现多种日志框架的对接mapping:参数、结果集的映射(基本类型、结合、自定义 JavaBean)ognl:动态 SQL 中提供 ognl 表达式支持parsing:XML解析工具,用来解析 mapper.xml 文件plugin:拦截器功能实现,使用代理模式实现拦截reflection:类元数据、反射功能实现代码scripting: 动态 SQL 语言实现,配置文件中 功能就是在这个包实现,借助 OGNL 表达式你也可以扩展自己的语言实现功能session:主要实现 SqlSession 功能,非常核心包transaction:事务功能实现,包装了数据库连接,处理数据库连接生命周期包括:连接创建,预编译,提交、回滚和关闭type:类型处理器,包括所有数据库类型对应 Java 类型的处理器,如果要实现自己类型处理器就需要实现包下的基础接口
文章
SQL  ·  XML  ·  缓存  ·  Java  ·  数据库连接  ·  数据处理  ·  Apache  ·  数据库  ·  数据格式  ·  mybatis
2023-01-19
SLF4J基本使用
1. 日志门面当我们的系统变的更加复杂的时候,我们的日志就容易发生混乱。随着系统开发的进行,可能会更新不同的日志框架,造成当前系统中存在不同的日志依赖,让我们难以统一的管理和控制。就算我们强制要求所有的模块使用相同的日志框架,系统中也难以避免使用其他类似spring,mybatis等其他的第三方框架,它们依赖于我们规定不同的日志框架,而且他们自身的日志系统就有着不一致性,依然会出来日志体系的混乱。所以我们需要借鉴JDBC的思想,为日志系统也提供一套门面,那么我们就可以面向这些接口规范来开发,避免了直接依赖具体的日志框架。这样我们的系统在日志中,就存在了日志的门面和日志的实现。常见的日志门面 :JCL、slf4j常见的日志实现:JUL、log4j、logback、log4j2日志门面和日志实现的关系:日志框架出现的历史顺序:log4j -->JUL-->JCL--> slf4j --> logback --> log4j22. SLF4J的使用简单日志门面(Simple Logging Facade For Java) SLF4J主要是为了给Java日志访问提供一套标准、规范的API框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如log4j和logback等。当然slf4j自己也提供了功能较为简单的实现,但是一般很少用到。对于一般的Java项目而言,日志框架会选择slf4j-api作为门面,配上具体的实现框架(log4j、logback等),中间使用桥接器完成桥接。官方网站: https://www.slf4j.org/SLF4J是目前市面上最流行的日志门面。现在的项目中,基本上都是使用SLF4J作为我们的日志系统。SLF4J日志门面主要提供两大功能:1. 日志框架的绑定 2. 日志框架的桥接2.1 SLF4J入门添加依赖<!--slf4 日志门面--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.0</version> </dependency> <!--slf4j 内置的简单实现--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>2.0.0</version> </dependency>编写代码public class Slf4jTest { public static final Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class); //快速入门 @Test public void test1() throws Exception { // 日志输出 LOGGER.error("error"); LOGGER.warn("warn"); LOGGER.info("info"); //默认级别 LOGGER.debug("debug"); LOGGER.trace("trace"); // 使用占位符输出日志信息 String name = "xdr630"; Integer age = 20; LOGGER.info("用户:{}, {}", name, age); // 将系统的异常信息输出 try { int i = 1/0; } catch (Exception e) { LOGGER.error("出现异常:", e); } } }2.2 为什么要使用SLF4J作为日志门面?使用SLF4J框架,可以在部署时迁移到所需的日志记录框架。SLF4J提供了对所有流行的日志框架的绑定,例如 log4j,JUL,Simple logging和NOP。因此可以在部署时切换到任何这些流行的框架。无论使用哪种绑定,SLF4J都支持参数化日志记录消息。由于SLF4J将应用程序和日志记录框架分离,因此可以轻松编写独立于日志记录框架的应用程序,而无需担心用于编写应用程序的日志记录框架。SLF4J提供了一个简单的Java工具,称为迁移器。使用此工具,可以迁移现有项目,这些项目使用日志框架(如:Jakarta Commons Logging(JCL)或log4j或Java.util.logging(JUL))到SLF4J。2.3 绑定日志的实现(Binding)如前所述,SLF4J支持各种日志框架。SLF4J发行版附带了几个称为“SLF4J绑定”的 jar 文件,每个绑定对应一个受支持的框架。使用slf4j的日志绑定流程:添加 slf4j-api 的依赖使用slf4j的API在项目中进行统一的日志记录绑定具体的日志实现框架绑定已经实现了slf4j的日志框架,直接添加对应依赖绑定没有实现slf4j的日志框架,先添加日志的适配器,再添加实现类的依赖slf4j有且仅有一个日志实现框架的绑定(如果出现多个默认使用第一个依赖日志实现)2.4 logback 整合 slf4jlogback-classic 中已经包含 logback-core 和 slf4j-api<!--logback 日志实现--> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>slf4j-nop表示日志开关,开启后表示slf4j不会使用任何日志实现框架,控制台就不会打印日志了<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-nop</artifactId> <version>1.7.25</version> </dependency>2.5 log4j 整合 slf4j<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.12</version> </dependency>log4j.properties# 指定 RootLogger 顶级父元素默认配置信息 # 指定日志级别=trace,使用的 apeender 为=console log4j.rootLogger = trace,console # 指定控制台日志输出的 appender log4j.appender.console = org.apache.log4j.ConsoleAppender # 指定消息格式 layout log4j.appender.console.layout = org.apache.log4j.PatternLayout # 指定消息格式的内容 log4j.appender.console.layout.conversionPattern = [%-10p]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n2.6 jul 整合 slf4j<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jdk14</artifactId> <version>1.7.25</version> </dependency>要切换日志框架,只需替换类路径上的slf4j绑定。例如,要从java.util.logging切换到log4j,只需将slf4j-jdk14-1.7.25.jar替换为slf4j-log4j12-1.7.12.jar即可。SLF4J不依赖于任何特殊的类装载。实际上,每个SLF4J绑定在编译时都是硬连线的, 以使用一个且只有一个特定的日志记录框架。例如,slf4j-log4j12-1.7.12.jar绑定在编译时绑定以使用log4j。在代码中,除了slf4j-api-1.7.12.jar之外,只需将选择的一个且只有一个绑定放到相应的类路径位置。不要在类路径上放置多个绑定。以下是一般概念的图解说明。3. 桥接旧的日志框架(Bridging)通常,依赖的某些组件依赖于SLF4J以外的日志记录API,也可以假设这些组件在不久的将来不会切换到SLF4J。为了解决这种情况,SLF4J附带了几个桥接模块,这些模块将对log4j,JCL和java.util.logging API的调用重定向,就好像它们是对SLF4J API一样。桥接解决的是项目中日志的遗留问题,当系统中存在之前的日志API,可以通过桥接转换到slf4j的实现先去除之前老的日志框架的依赖添加SLF4J提供的桥接组件为项目添加SLF4J的具体实现迁移的方式:如果要使用SLF4J的桥接器,替换原有的日志框架,那么需要做的第一件事情,就是删除掉原有项目中的日志框架的依赖,然后替换成SLF4J提供的桥接器。<!--配置log4j日志桥接器,包含slf4j-api--> <dependency> <groupId>org.slf4j</groupId> <artifactId>log4j-over-slf4j</artifactId> <version>1.7.25</version> </dependency> <!--logback 日志实现,包含 core 和 slf4j-api--> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>注意问题:jcl-over-slf4j.jar和 slf4j-jcl.jar不能同时部署。前一个jar文件将导致JCL将日志系统的选择委托给SLF4J,后一个jar文件将导致SLF4J将日志系统的选择委托给JCL,从而导致无限循环log4j-over-slf4j.jar和slf4j-log4j12.jar不能同时出现jul-to-slf4j.jar和slf4j-jdk14.jar不能同时出现所有的桥接都只对Logger日志记录器对象有效,如果程序中调用了内部的配置类或者是Appender,Filter等对象,将无法产生效果3.1 SLF4J原理解析SLF4J通过LoggerFactory加载日志具体的实现对象。LoggerFactory在初始化的过程中,会通过performInitialization()方法绑定具体的日志实现。在绑定具体实现的时候,通过类加载器,加载org/slf4j/impl/StaticLoggerBinder.class所以,只要是一个日志实现框架,在org.slf4j.impl包中提供一个自己的StaticLoggerBinder类,在其中提供具体日志实现的LoggerFactory就可以被SLF4J所加载
文章
Java  ·  数据库连接  ·  API  ·  Spring  ·  mybatis
2023-01-22
常见漏洞的应对方式(一)
    之前的一篇 常见漏洞总结 介绍了工作中可能常见的一些漏洞,今天来学习下关于这些漏洞的一些应对方式。 1. sql注入    1)JDBC的预编译PreparedStatement ps = conn.preparedStatement(sql); ps.setString(1,username); ps.setString(2,password); ResultSet rs = ps.executeQuery();    如上图示例:使用预编译PreparedStatement,通过setXXX方法传递变量,可解决SQL注入问题。    2)MyBatis    示例如下:select * from table where username = ${username} and passeord = #{password};    mybatis中,使用 # 可以解决注入问题。如上图示例,username存在注入风险,而password不存在。    3)总结    两种防注入方式实际都是通过对输入进行预处理,使输入都变为一个字符串,从而忽略sql语句的拼接来达到防注入的效果。2. XSS漏洞    1)过滤注意事项: 有时过滤会导致一些错误处理,例如将alice's 过滤为alices; 有时需要进行多次过滤操作,例如<htm<html>l> 过滤掉<html>之后还存在<html>标签 需要注意多个过滤器生效的先后顺序。多个过滤器一起生效时,有可能后生效的会导致先生效的过滤失效。    2)输入输出编码    输入编码往往有全局的解决方案,但是对于持久性的xss,已经入库之后,就难以使用编码来处理。    3)恶意流量拦截    通过一些前置的恶意流量检测和拦截,来提前预防攻击产生    4)CSP解决方案    内容安全策略(CSP):网站通过发送一个 CSP 头部,来告诉浏览器什么是被授权执行的与什么是需要被禁止的。通过配置浏览器渲染规则,来限制浏览器执行的js代码、html标签等。3. CSRF漏洞    1)请求附带随机参数    每次请求带一次有效的随机数(隐藏 input)。    2)避免跨域请求    校验origin,referer;post发送json数据    3)跨域策略    Double Submit Cookie4. 文件上传和下载    1)文件上传限制目录不可执行上传文件的类型和大小检查    2)文件下载禁止用户自定义文件路径对用户请求的文件名进行安全处理
文章
SQL  ·  JSON  ·  安全  ·  JavaScript  ·  Java  ·  数据库连接  ·  Go  ·  数据格式  ·  mybatis
2023-01-20
【大数据系列之JDBC】(七):JDBC解决字段名和Java中对象名不一致
数据库中的字段名经常和我们Java中的Bean对象的属性名不一致,这样会导致使用反射为对象赋值出现如下问题:对于该问题,Mybatis中可以使用注解来解决,但是最基本的JDBC操作是无法使用注解进行解决,这时可以使用起别名的方式进行解决,也就是在执行SQL语句时,可以将返回的字段名起别名将其和Bean对象中的属性名一致。然后使用 rsmd.getColumnLabel() 获取结果集对应的别名,而不是使用 rsmd.getColumnName() 获取结果集的列名。@Test public void test() { String sql = "select order_id orderId, order_name orderName, order_date orderDate from `order` where order_id = ?"; Order order = orderForQuery(sql, "1"); System.out.println(order); } public Order orderForQuery(String sql, Object... args) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; Order order = null; try { // 1.获取连接 conn = JDBCUtils.getConnection(); // 2.预编译SQL语句 ps = conn.prepareStatement(sql); // 3.填充占位符 for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]); } // 4.获取结果集 rs = ps.executeQuery(); // 5.获取结果集元数据对象 ResultSetMetaData rsmd = rs.getMetaData(); // 6.获取结果集列数 int columnCount = rsmd.getColumnCount(); // 7.处理结果集 if (rs.next()) { order = new Order(); for (int i = 0; i < columnCount; i++) { // 8.获取结果集的值 Object columnValue = rs.getObject(i + 1); // 9.获取结果集的别名 String columnLabel = rsmd.getColumnLabel(i + 1); // 10.通过反射为对象赋值 Field field = Order.class.getDeclaredField(columnLabel); field.setAccessible(true); field.set(order, columnValue); } } } catch (Exception e) { e.printStackTrace(); } finally { // 11.关闭资源 JDBCUtils.closeResource(conn, ps, rs); } return order; }Bean对象
文章
SQL  ·  Java  ·  大数据  ·  数据库连接  ·  数据库  ·  mybatis
2023-01-20
Java开发 - Spring Test知多少?
前言在前文中,我们也使用了测试代码来进行简单的单元测试,但是我们会发现,里面有大量的重复代码,实际给我们的体验并不是太好,所以这篇,我们来学习Spring Test,Spring Test不仅仅限于在Mybatis框架,只要是基于Spring的框架的都可以使用Spring Test,使用Spring Test,将给测试模块带来质的改善,大大提高了自测的效率。接下来,我们就来学习Spring Test的用法和注意事项吧。Spring Test的作用在普通测试环境下,我们在使用Spring的时候,需要手动加载Spring配置,手动从Spring容器中获取对象,前文中的使用全是如此,这也就违背了我们使用Spring框架的意愿:自动创建对象,自动管理对象。我们把这种用法叫自动装配。Spring还有一个用处,使用@Sql注解,此注解可在测试类方法之前定义,提前或延后执行某段sql语句,在测试中也经常使用。在项目中加入Spring Test添加依赖<!--Spring Test依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.14</version> </dependency>Spring Test无法单独工作,仍需配合其他测试依赖项和其他Spring依赖一起使用,可参照Mybatis一文中的依赖进行添加,亦可在原项目中直接操作。但要注意,要和其他Spring依赖项的版本保持一致,切记。创建测试类package cn.codingfire.mybatis; public class MybatisTest { }此时需在测试类上添加@SpringJUnitConfig注解:package cn.codingfire.mybatis; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @SpringJUnitConfig(SpringConfig.class) public class MybatisTest { }接着我们可以在此类中添加Spring的配置类,这样,在此类中任何方法之前,都会先加载Spring的配置类,Spring容器中存在的类就都可以实现自动装配了。我们以环境变量为例:package cn.codingfire.mybatis; import cn.codingfire.mybatis.config.SpringConfig; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @SpringJUnitConfig(SpringConfig.class) public class MybatisTest { @Autowired Environment env; @Test public void testEnvironment() { System.out.println(env.getProperty("datasource.url")); System.out.println(env.getProperty("datasource.driver")); System.out.println(env.getProperty("datasource.username")); System.out.println(env.getProperty("datasource.password")); } }接着运行此测试方法,查看输出: 编辑已经成功输出了我们在properties文件中配置的信息。对比之前Mybatis中的测试方法如下:@Test public void loadBasicInfo() { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class); ConfigurableEnvironment environment = ac.getEnvironment(); System.out.println(environment.getProperty("datasource.url")); System.out.println(environment.getProperty("datasource.driver")); System.out.println(environment.getProperty("datasource.username")); System.out.println(environment.getProperty("datasource.password")); ac.close(); }先获取ac,再获取environment,最后再关闭ac,简化了太多步骤。使用Spring Test,我们只需关注测试的内容本身,而不用去管环境的问题,效果要更好。再增删改查时也不需要再关注开头和结尾的那几段代码,这种重复性的操作被省略,也是自动装配的精髓之一。关于@Autowired注解,就是自动装配的意思,我们可以尝试着给其他的对象添加此注解:编辑会看到AdminMapper报一个错,这里有个小知识点。这是因为编译器问题导致无法识别,解决办法是在AdminMapper的类中添加@Repository注解,回来后再看,正常来说报错会消失,但有的人的不会消失,可在注解里添加required属性为false:编辑 此时,问题已经解决了,它的意思是,能装配上就装,不能装配也不强求。有意思的是,即使你不管这个报错,方法也可正常运行,不存在任何影响。Spring Test下的测试方法我们以插入方法为例,做个前后对比。未使用Spring Test的插入方法测试:@Test public void testInsert() { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class); AdminMapper adminMapper = ac.getBean(AdminMapper.class); Admin admin = new Admin(); admin.setUsername("admin04"); admin.setPassword("123456"); adminMapper.insert(admin); ac.close(); }使用了Spring Test的插入方法测试 :@Test public void testInsert() { Admin admin = new Admin(); admin.setUsername("admin04"); admin.setPassword("123456"); adminMapper.insert(admin); }前后对比明显,自动装配后,adminMapper由系统创建管理,可在类中直接使用,简化了代码。 @Sql注解@Sql注解的作用和注意事项Spring Test测试类中,还可以使用@Sql注解,他可以加载某些脚本.sql的脚本,可以在测试前后执行一些给定的sql语句。它的作用是可以在测试时进行反复测试,在Mybatis中,我们在测试时,有时为了使mapper的方法运行成功,需要运行插入的方法,这就很不友好了,增加了测试的成本,比如我删除某条数据后,表中没有数据,我要再执行删除操作前,必须要再插入一条数据,否则会报错,而我使用@Sql注解,就可以解决这个问题,使得每次测试不需要再关注其他的方法。使用此注解要注意几个问题:@Sql注解可以添加在单独的方法中,仅对此方法有效,也可添加在测试类上,对类中所有的方法有效。如果类和方法上都添加了相同的@Sql注解,仅方法上的生效可方法前执行.sql脚本,也可方法后执行.sql脚本,通过executionPhase属性来管理@Sql注解可添加多个@Sql注解怎么用首先,我们需要先创建一个.sql文件,选择file,创建一个truncate.sql:编辑在test下的resoutces文件中创建,在此文件中可以看到和再sql工具中一样,是有sql提醒的。 truncate的意思是截断,在sql中意味着清空整张表。我们知道,数据库表中不允许插入相同的两条数据,否则就会报重复的错误,使用此注解,在每次插入前都清空整张表就可以频繁测试,看代码:@Sql(scripts = {"classpath:truncate.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Test public void testInsert() { Admin admin = new Admin(); admin.setUsername("admin04"); admin.setPassword("123456"); adminMapper.insert(admin); }可以在插入前清空整张表,以达到频繁插入的测试。以删除为例,我们想删除的时候数据库表中一直有数据。此时,也可以使用此注解,接下来我们来说说怎么同时使用多个.sql脚本,首先创建一个插入的.sql脚本:编辑 看代码:@Sql(scripts = {"classpath:truncate.sql", "classpath:insert.sql"}) @Test public void testDelete() { adminMapper.deleteById(1L); }每次执行次方法都是成功的。如果你想在方法执行后再执行某些sql的话,可以设置@Sql的executionPhase属性为Sql.ExecutionPhase.AFTER_TEST_METHOD:executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD断言我们通常在测试类中使用Assertions类的静态方法对测试结果进行预测,帮助我们发现代码中可能存在的问题,一旦不符合预测的正确结果就会报错,大大提高代码的正确性。常用的一些断言方法有:assertEquals():断言匹配(相等)assertNotEquals():断言不匹配(不相等)assertTrue():断言为“真”assertFalse():断言为“假”assertNull():断言为nullassertNotNull():断言不为nullassertThrows():断言抛出异常assertDoesNotThrow():断言不会抛出异常其他接下来我们挑几个在代码中来看使用效果。assertEquals():@Sql(scripts = {"classpath:truncate.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) @Test public void testInsert() { Admin admin = new Admin(); admin.setUsername("admin04"); admin.setPassword("123456"); int index = adminMapper.insert(admin); System.out.println(index); Assertions.assertEquals(1,index); }Assertions.assertEquals 有两个参数,第一个是expected,是期望的值,第二个是ectual,是实际的值,如果预测的不对,就会报错,正确则没有任何反应。assertTrue()还以插入为例,看代码:@Sql(scripts = {"classpath:truncate.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) @Test public void testInsert() { Admin admin = new Admin(); admin.setUsername("admin04"); admin.setPassword("123456"); int index = adminMapper.insert(admin); System.out.println(index); boolean isThanZero = index > 0 ? true : false; Assertions.assertTrue(isThanZero); }断言isThanZero,即影响的行数大于0,则说明插入成功,否则将报异常。assertNull()以根据id获取数据为例:@Test public void getById() { Admin admin = adminMapper.getById(10L); Assertions.assertNull(admin); }id为10的数据表中没有,所以语言admin为null,是正确的,符合我们的预期,不会报错。assertThrows()前面我们说过重复插入数据会把哦重复插入的异常,这时就不能在插入前清空表了,我们以此为例,看代码:@Test public void testInsert() { Admin admin = new Admin(); admin.setUsername("admin04"); admin.setPassword("123456"); Assertions.assertThrows(DuplicateKeyException.class, () -> { adminMapper.insert(admin); }); }正常来说,连续执行两次,就会抛出重复插入的异常,但是我们做了断言后,就不会有任何输出,反而会在第一次执行时抛出下面这段异常:org.opentest4j.AssertionFailedError: Expected org.springframework.dao.DuplicateKeyException to be thrown, but nothing was thrown.意思是说,我们预测会抛出重复的异常,但是什么也没有抛出。这是正常的,因为第一次插入成功了。到这里,断言就写完了,上面列出来的每一类中的一个都给出了案例 ,照葫芦画瓢,对另一个取反就是另一个,相信聪明如大家已经知道该怎么用了,不再赘述。结语最近几天,这篇算是最短的了,写起来也最省劲,用了不到半天就写完了,虽然简单,但是里面的知识却很重要,最好结合前面的SSM框架一起来看和使用,可以达到事半功倍的效果。代码要练习,光看是不行的,不上手,就看不到输出,就容易忽略一些细节,希望大家都能学的贼溜,明年拿高薪。
文章
SQL  ·  Java  ·  数据库连接  ·  测试技术  ·  编译器  ·  数据库  ·  Spring  ·  mybatis  ·  容器
2023-01-20
Mybatis-Pagehelper详细解析及优化插件开发
背景项目数据库数据量较大,分页查询要很久,所以要对分页优化,项目使用的分页是mybatis的Pagehelper,于是在Pagehelper的基础上进行了本次分页查询的优化Mybatis-Pagehelper优化是基于mybatis-Pagehelper的,我们先看一下mybatis-Pagehelper这个插件,他是怎么实现mybatis分页的,比如,基本上我们每个人在分页查询时都会看到先查询count 再 具体查询,那他们在哪个环节查询,这里就有答案,首先我们先对这个插件进行集成集成<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> 配置pagehelper.helperDialect=mysql pagehelper.reasonable=false 使用 使用起来很简单,这样就可以完成了public PageInfo<MessageSmsSendTaskVO> queryByPage(MessageSmsSendTaskQuery messageSmsSendTaskQuery) { messageSmsSendTaskQuery.enablePage(); List<MessageSmsSendTaskVO> list = this.messageSmsSendTaskMapper.queryList(messageSmsSendTaskQuery); STranslationConvertUtil.convertList(list); return toPageInfo(list); } /** * 初始化分页信息,且开启分页线程控制器 */ public <E> Page<E> enablePage() { pageInfoInit(); return PageHelper.startPage(pageNum, pageSize); } /** * 初始化分页信息 */ public void pageInfoInit() { if (pageNum == null || pageNum < 1) { pageNum = 1; } if (pageSize == null || pageSize < 0) { pageSize = 15; } } 4. 源码分析 我们可以在idea上直接看源码,也可以在码云下载源码项目分析,项目地址 : http://git.oschina.net/free/Mybatis_PageHelper我们从PageHelper.startPage(pageNum, pageSize)这个调用开始分析 他调用的是这个方法public static Page startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) { Page<E> page = new Page<E>(pageNum, pageSize, count); page.setReasonable(reasonable); page.setPageSizeZero(pageSizeZero); //当已经执行过orderBy的时候 Page<E> oldPage = getLocalPage(); if (oldPage != null && oldPage.isOrderByOnly()) { page.setOrderBy(oldPage.getOrderBy()); } setLocalPage(page); return page;}其实这个方法就一个目的就是初始化Page对象,也就是setLocalPage(page)中完成初始化public abstract class PageMethod { protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>(); protected static boolean DEFAULT_COUNT = true; /** * 设置 Page 参数 * * @param page */ protected static void setLocalPage(Page page) { LOCAL_PAGE.set(page); }我们看到最终把实例化好的page对象放入到了ThreadLocal<Page>中,ThreadLocal之前我有写文章说过,他是线程本地变量,使用他不会存在并发问题,因为他会为每一个放在他里面的对象都开辟一块独立的内存空间。 这个方法就结束了,我们发现后面只调用了new PageInfo<>(list)方法,并没有看到任何关于count查询的方法,那么这部分查询在哪里呢,其实是在实现了拦截器的PageInterceptor中 public class PageInterceptor implements Interceptor { private volatile Dialect dialect; private String countSuffix = "_COUNT"; protected Cache<String, MappedStatement> msCountMap = null; private String default_dialect_class = "com.github.pagehelper.PageHelper"; @Override public Object intercept(Invocation invocation) throws Throwable { try { Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; Object parameter = args[1]; RowBounds rowBounds = (RowBounds) args[2]; ResultHandler resultHandler = (ResultHandler) args[3]; Executor executor = (Executor) invocation.getTarget(); CacheKey cacheKey; BoundSql boundSql; //由于逻辑关系,只会进入一次 if (args.length == 4) { //4 个参数时 boundSql = ms.getBoundSql(parameter); cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql); } else { //6 个参数时 cacheKey = (CacheKey) args[4]; boundSql = (BoundSql) args[5]; } checkDialectExists(); //对 boundSql 的拦截处理 if (dialect instanceof BoundSqlInterceptor.Chain) { boundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.ORIGINAL, boundSql, cacheKey); } List resultList; //调用方法判断是否需要进行分页,如果不需要,直接返回结果 if (!dialect.skip(ms, parameter, rowBounds)) { //判断是否需要进行 count 查询 if (dialect.beforeCount(ms, parameter, rowBounds)) { //查询总数 Long count = count(executor, ms, parameter, rowBounds, null, boundSql); //处理查询总数,返回 true 时继续分页查询,false 时直接返回 if (!dialect.afterCount(count, parameter, rowBounds)) { //当查询总数为 0 时,直接返回空的结果 return dialect.afterPage(new ArrayList(), parameter, rowBounds); } } resultList = ExecutorUtil.pageQuery(dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey); } else { //rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页 resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql); } return dialect.afterPage(resultList, parameter, rowBounds); } finally { if(dialect != null){ dialect.afterAll(); } }} 这个方法里面实现了count查询和方法查询,但是其实它不仅仅是只有分页查询调用,部分页面也会调用,那他又是被谁调用的,我们继续前推 public class Plugin implements InvocationHandler { private final Object target; private final Interceptor interceptor; private final Map<Class<?>, Set<Method>> signatureMap; private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) { this.target = target; this.interceptor = interceptor; this.signatureMap = signatureMap; } public static Object wrap(Object target, Interceptor interceptor) { Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); Class<?>[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0) { return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { Set<Method> methods = signatureMap.get(method.getDeclaringClass()); if (methods != null && methods.contains(method)) { return interceptor.intercept(new Invocation(target, method, args)); } return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } }我们看到调用他的类是Plugin类,而且是jdk动态代理来实现的,所以真相大白了,这个就是mybatis通过动态代理查询。回到PageInterceptor # intercept方法,他取出参数后通过skip方法判断要不要进行分页,我们看下skip方法的实现 ``` public boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) { if (ms.getId().endsWith(MSUtils.COUNT)) { throw new RuntimeException("在系统中发现了多个分页插件,请检查系统配置!"); } Page page = pageParams.getPage(parameterObject, rowBounds); if (page == null) { return true; } else { //设置默认的 count 列 if (StringUtil.isEmpty(page.getCountColumn())) { page.setCountColumn(pageParams.getCountColumn()); } autoDialect.initDelegateDialect(ms); return false; } } ``` 他会调用getPage方法获取page,老铁们这个就和开头对应上了,因为开头时我们只是做一个赋值操作,没有做其他的,而这个通过判断有没有值来判断是不是分页,所以很合理。 如果不进行分页,那么就回调用 ``` resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql); ``` 这个方法就是获取mapper文件中的sql来执行查询了。这些就是分页插件的基本原理了,现在回到最开始里的问题,数据量很大,我们要提速,要基于mybatis-Pagehelper新写功能 基于mybatis-Pagehelper的插件新建MidPageHelper类 public class MidPageHelper extends PageMethod implements Dialect, BoundSqlInterceptor.Chain这个类和PageHelper一样,也就是在调用时初始化PageHelperHandler类这个类里面就是访写PageInterceptor类 @Slf4j @Intercepts({ @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}), }) public class PageHelperHandler implements MidExpandMybatisInterceptor { private volatile Dialect dialect; private String countSuffix = "_MID_COUNT"; protected Cache<String, MappedStatement> msCountMap = null; private String default_dialect_class = "com.dst.mid.expand.page.MidPageHelper"; @Override public Object intercept(Invocation invocation) throws Throwable {我们刚刚说resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);这个方法是最后的执行方法,其实这次分页的优化也是对这里进行处理 我们在前面加一些逻辑,增加pageQuery方法public static <E> List<E> pageQuery方法(Dialect dialect, Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql, CacheKey cacheKey) throws SQLException, JSQLParserException { //生成分页的缓存 key CacheKey pageKey = cacheKey; //处理参数对象 parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey); //调用方言获取分页 sql //String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey); String pageSql = getPageQuerySql(boundSql.getSql(), PageHelperThreadLocal.getPage()); BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter); Map<String, Object> additionalParameters = ExecutorUtil.getAdditionalParameter(boundSql); //设置动态参数 for (String key : additionalParameters.keySet()) { pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key)); } //对 boundSql 的拦截处理 if (dialect instanceof BoundSqlInterceptor.Chain) { pageBoundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.PAGE_SQL, pageBoundSql, pageKey); } //执行分页查询 return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql); }getPageSql方法就是这次优化的核心public static String getPageQuerySql(String sql, Page page) throws JSQLParserException { String innerSql = getInnerSql(sql); String fromSql = getFromSql(sql); String limit; if (page.getStartRow() == 0) { limit = "\n LIMIT ? "; } else { limit = "\n LIMIT ?, ? "; } String querySql = String.format("SELECT * FROM (%s) from_tbl INNER JOIN (%s \n %s) inner_tbl on from_tbl.id = inner_tbl.id", fromSql, innerSql, limit); log.info("querySql: {}", querySql); return querySql; }原理其实就是,先把要查看数据的id查出来,再把id结果集取一个别名和真正要查的所有数据关联获取结果,id在数据库里面是b+树中所以就算排序他也很快,如果用之前的方式,直接通过limit获取结果集再加上排序,时间就很长了,经过测试600w条数据从一分钟,优化到花费3s,缺点是对复杂查询还是有些不兼容,不过个人觉得瑕不掩瑜了。
文章
Java  ·  数据库连接  ·  数据库  ·  mybatis
2023-01-20
1 2 3 4 5 6 7 8 9
...
20
跳转至:
数据库
252243 人关注 | 50616 讨论 | 93899 内容
+ 订阅
  • 心动不如行动,基于Docker安装关系型数据库PostgrelSQL替代Mysql
  • Java高手速成│实战:应用数据库和GUI开发产品销售管理软件(1)
  • 使用有感
查看更多 >
开发与运维
5597 人关注 | 131348 讨论 | 299146 内容
+ 订阅
  • 别让你的服务器(vps)沦为肉鸡(ssh暴力破解),密钥验证、双向因子登录值得拥有
  • 锁对象
  • 销撤销
查看更多 >
微服务
22987 人关注 | 11295 讨论 | 32878 内容
+ 订阅
  • 在虚拟机中部署jeecg微服务--Docker镜像启动微服务项目不显示gateway界面问题
  • Spring boot 配置访问日志
  • 《基于RocketMQ Connect 构建全新数据流转处理平台》电子版地址
查看更多 >
大数据
187960 人关注 | 29023 讨论 | 79762 内容
+ 订阅
  • 心动不如行动,基于Docker安装关系型数据库PostgrelSQL替代Mysql
  • 阿里云国际站DDOS和阿里云国内站ddos防护有什么区别吗?
  • 数据结构 | 排序算法——插入排序与希尔排序
查看更多 >
安全
1179 人关注 | 23942 讨论 | 80523 内容
+ 订阅
  • 别让你的服务器(vps)沦为肉鸡(ssh暴力破解),密钥验证、双向因子登录值得拥有
  • 销撤销
  • 阿里云国际站代理商:美国和香港服务器怎么配置采购?
查看更多 >