Java系统开发从入门到精通第三讲(文字版)

createh51个月前 (02-06)技术教程23

下来我们进入数据持久化的部分,对于一个真实的业务系统,能够正常的运转离不开数据的持久化。在数据持久化这块,目前主流的还是关系型数据库(RDBMS),NoSQL(NewSQL)也有了长足发展,特别在大数据领域。

JDBC

数据持久化这块,javaEE推出了JDBC规范,基于JDBC规范,只要不同的数据库支持对应协议,业务系统就能和数据库服务器(MySQL、Oracle、DB2等)交互



JPA

JPA(Java Persistence API)用于对象持久化的 API,是 Java EE 5.0 平台标准的 ORM 规范,使得应用程序以统一的方式访问持久层。

Hibernate 是符合 JPA 规范的,而 MyBatis 却不符合,因为 MyBatis 还是需要写 SQL 的



MyBatis

MyBatis是一个不屏蔽SQL且提供动态SQL、接口式编程和简易SQL绑定POJO的半自动化框架,它的使用十分简单,而且能非常容易定制SQL,以提高网站性能,因此在移动互联网兴起的时代,它占据了强势的地位。

MyBatis的基本概念:数据库表对应的java对象(POJO)、Mapper(映射器,一些绑定映射语句的接口)、映射语句(XML和注解两种方式,过去XML方式更为流行)、Mybatis配置

  • 数据表结构
CREATE TABLE `tz_user` (
  `user_id` varchar(36) NOT NULL DEFAULT '' COMMENT 'ID',
  `nick_name` varchar(50) DEFAULT NULL COMMENT '用户昵称',
  `real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
  `user_mail` varchar(100) DEFAULT NULL COMMENT '用户邮箱',
  `login_password` varchar(255) DEFAULT NULL COMMENT '登录密码',
  `pay_password` varchar(50) DEFAULT NULL COMMENT '支付密码',
  `user_mobile` varchar(50) DEFAULT NULL COMMENT '手机号码',
  `modify_time` datetime NOT NULL COMMENT '修改时间',
  `user_regtime` datetime NOT NULL COMMENT '注册时间',
  `user_regip` varchar(50) DEFAULT NULL COMMENT '注册IP',
  `user_lasttime` datetime DEFAULT NULL COMMENT '最后登录时间',
  `user_lastip` varchar(50) DEFAULT NULL COMMENT '最后登录IP',
  `user_memo` varchar(500) DEFAULT NULL COMMENT '备注',
  `sex` char(1) DEFAULT 'M' COMMENT 'M(男) or F(女)',
  `birth_date` char(10) DEFAULT NULL COMMENT '例如:2009-11-27',
  `pic` varchar(255) DEFAULT NULL COMMENT '头像图片路径',
  `status` int(1) NOT NULL DEFAULT '1' COMMENT '状态 1 正常 0 无效',
  `score` int(11) DEFAULT NULL COMMENT '用户积分',
  PRIMARY KEY (`user_id`),
  UNIQUE KEY `ud_user_mail` (`user_mail`),
  UNIQUE KEY `ud_user_unique_mobile` (`user_mobile`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';
  • 实体类(示例)
@Data
public class User implements Serializable {
    private String userId;//id
    private String realName;//用户名
    private String loginPassword;//密码
}

这是实体对象,和数据表字段一一对应(并且属性名称和数据表字段名称存在规则映射,例如:user_id 为 userId)

这里也体现了”约定“的威力,基于约定很多事情处理都变得异常简单

  • Mapper XML
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.easycloud.daomall.show.dao.UserMapper">
    <resultMap id="userMap" type="User">
        <id property="userId" column="user_id"></id>
        <result property="realName" column="real_name" />
        <result property="loginPassword" column="login_password" />
    </resultMap>
    <select id="findAll" resultMap="userMap">
        SELECT * FROM tz_user
    </select>
</mapper>

这是Mybatis中的核心:

因为SQL命名规范(多以”_“连接)和java(多以驼峰)命名规范不一致,所以通过resultMap标签来做java属性和数据表字段的对应关系;

关于映射,Mybatis提供了三种处理方式:

1、如上代码所示,手动指定(麻烦);

2、SQL语句设置别名(稍显麻烦),例如:SELECT user_id as userId, real_name as realName

3、设置开启驼峰命名map-underscore-to-camel-case: true(基于“约定”的处理);

select标签中的resultMap属性,描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素,它和resultMap标签中的id属性保持一致;

sql标签,可被其它语句引用的可重用语句块; insert标签,映射插入语句; update标签,映射更新语句; delete标签,映射删除语句; select标签,映射查询语句;

对应SQL的CRUD

  • Mapper接口
@Mapper
public interface UserMapper {
    public List<User> findAll();
}
  • MyBatis配置
  • 使用Spring Boot,Mybatis配置非常简单
  • #mybatis的相关配置
    mybatis:
    #mapper配置文件
    mapper-locations: classpath:mapper/*.xml
    type-aliases-package: com.easycloud.daomall.show.model
    #开启驼峰命名
    configuration:
    map-underscore-to-camel-case: true
  • 其中type-aliases-package指定实体类所在的包,这样在设置”type、parameterType、resultType“属性值时可只用类名。
  • api暴露

然后按照前面讲的Spring RESTful service暴露,就可以查看访问数据库的效果了



MyBatis-Plus

https://baomidou.com

在实际的业务编写中,你会发现要写大量的CRUD Mapper XML和对应的接口定义,但其实对于数据表的操作基本可以抽象为CRUD(包括分页和高级检索)这几种类型,MyBatis-Plus就应运而生。

使用MyBatis-Plus后,我们看看可以省略哪些代码:

  • application.yml中的MyBatis配置
  • Mapper XML文件
  • Mapper java接口中的方法定义,然后继承BaseMapper
  • public interface UserMapper extends BaseMapper<User> {
  • 内置方法说明参见:https://baomidou.com/pages/49cc81/
  • 调用Mapper的地方改为selectList(null)
  • 实体类名和数据表名不存在规则一致性的话,需要加上TableName注解(例如我们的代码示例)
  • @Data
    @TableName("tz_user")
    public class User implements Serializable {
  • MyBatis-Plus处处体现了”约定优于配置“的原则

分页实现

分页插件,在实际的业务中,分页是一个常见的场景,使用MyBatis-Plus分页,需要启用分分页插件

@Configuration
public class MyBatisConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

分页代码

IPage<User> page = new Page<User>(start, rows);
return userMapper.selectPage(page,null).getRecords();

其中Page对象,第一个参数表示是第几页,第二个参数表示每页有几条数据

条件构造

https://baomidou.com/pages/10c804/

MyBatis-Plus中使用Wrapper实现,支持查询、更新的where条件。使用Lambda 表达式更加的简洁。

QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(User::getUserId, "111");

上面就是查询userId为111的用户

其它特性

作为一个可扩展的设计,数据表ID不应该使用数据库自增ID(例如:分库分表就会出现ID重复问题),所以MyBatis-Plus提供了ID生成的增强

  • 注解使用ID生成策略
@TableId()
private String userId;

不做任何设置,使用其默认内置的雪花算法

它还支持其他几种模式,非必要使用默认方式足矣

ASSIGN_ID(雪花算法) 如果不设置类型值,默认则使用IdType.ASSIGN_ID策略(自3.3.0起)。该策略会使用雪花算法自动生成主键ID,主键类型为长或字符串(分别对应的MySQL的表字段为BIGINT和VARCHAR)

ASSIGN_UUID(排除中划线的UUID) 如果使用IdType.ASSIGN_UUID策略,并重新自动生成排除中划线的UUID作为主键。主键类型为String,对应MySQL的表分段为VARCHAR(32)

AUTO(数据库ID自增)

INPUT(插入前自行设置主键值)

无(无状态) 如果使用IdType.NONE策略,表示未设置主键类型(注解里等于跟随上下,左右里约等于INPUT)

策略全局设置

mybatis-plus.global-config.db-config.id-type=值
  • 调用
User user = new User();
user.setUserRegtime(LocalDateTime.now());
user.setModifyTime(LocalDateTime.now());

userService.save(user);

其他前置知识

Java

Lambda

我们知道java8是一个大改动的版本,也让java的能力有了更进一步的提升,并且很多新特性应用广泛,Lambda表达式就是其中之一,在MyBatis章节我们就有使用Lambda表达式,第一感觉就是书写更加的方便,非常的强大。下来我们就对Lambda表达式做一定的了解。

Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。

Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

使用 Lambda 表达式可以使代码变的更加简洁紧凑。

Lambda 表达式的语法格式如下:

(parameters) -> expression
或
(parameters) ->{ statements; }

Lambda 表达式的简单例子:

// 1. 不需要参数,返回值为 5  
() -> 5  
  
// 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 * x  
  
// 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x – y  
  
// 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y  
  
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
(String s) -> System.out.print(s)

在 LambdaTester.java 文件输入以下代码:

package com.easycloud.javacase.lambda;

public class LambdaShow {
    public static void main(String args[]){
        LambdaShow tester = new LambdaShow();

        // 类型声明
        MathOperation addition = (int a, int b) -> a + b;

        // 不用类型声明
        MathOperation subtraction = (a, b) -> a - b;

        // 大括号中的返回语句
        MathOperation multiplication = (int a, int b) -> { return a * b; };

        // 没有大括号及返回语句
        MathOperation division = (int a, int b) -> a / b;

        System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
        System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
        System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
        System.out.println("10 / 5 = " + tester.operate(10, 5, division));

        // 不用括号
        GreetingService greetService1 = message ->
                System.out.println("Hello " + message);

        // 用括号
        GreetingService greetService2 = (message) ->
                System.out.println("Hello " + message);

        greetService1.sayMessage("Runoob");
        greetService2.sayMessage("Google");
    }

    interface MathOperation {
        int operation(int a, int b);
    }

    interface GreetingService {
        void sayMessage(String message);
    }

    private int operate(int a, int b, MathOperation mathOperation){
        return mathOperation.operation(a, b);
    }
}

使用 Lambda 表达式需要注意以下两点:

  • Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。在上面例子中,我们使用各种类型的Lambda表达式来定义MathOperation接口的方法。然后我们定义了sayMessage的执行。
  • Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。

我们来看一下,引入Lambda表达式对以前以前繁琐写法的改进,

例如常见的排序算法:

java8以前:

// 使用 java 7 排序
   private void sortUsingJava7(List<String> names){   
      Collections.sort(names, new Comparator<String>() {
         @Override
         public int compare(String s1, String s2) {
            return s1.compareTo(s2);
         }
      });
   }

java8的Lambda:

// 使用 java 8 排序
   private void sortUsingJava8(List<String> names){
      Collections.sort(names, (s1, s2) -> s1.compareTo(s2));
   }

再例如(多线程编程):

java8以前:

Runnable r2 = new Runnable(){ 
  public void run(){ 
    System.out.println("Hello World 2"); 
  } 
};
//执行Runnable方法 
public static void process(Runnable r){ 
  r.run(); 
}

//打印 "Hello World 1" 
process(r1); 
//打印 "Hello World 2" 
process(r2);

java8的Lambda:

Runnable r1 = () -> System.out.println("Hello World 1");
Runnable r2 = () -> System.out.println("Hello World 2");

另外我们在Mybatis的QueryWrapper中使用了”方法引用“,用一对冒号::代表方法引用,使语言调用更加紧凑简洁,减少冗余代码。

  queryWrapper.lambda().eq(User::getUserId, "111")

和Lambda配合使用的场景很多,上面演示的接口sayMessage()和Runnable接口,都是函数式接口(Functional Interface)

函数式接口

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。

函数式接口可以被隐式转换为 lambda 表达式。

Lambda 表达式和方法引用(实际上也可认为是Lambda表达式)上。

JDK 1.8 之前已有的函数式接口:

  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.security.PrivilegedAction
  • java.util.Comparator
  • java.io.FileFilter
  • java.nio.file.PathMatcher
  • java.lang.reflect.InvocationHandler
  • java.beans.PropertyChangeListener
  • java.awt.event.ActionListener
  • javax.swing.event.ChangeListener

JDK 1.8 新增加的函数接口:

  • java.util.function

我们再来看一个具体的示例:

Predicate <T> 接口是一个函数式接口,它接受一个输入参数 T,返回一个布尔值结果。

该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非)。

该接口用于测试对象是 true 或 false。


import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
 
public class Java8Tester {
   public static void main(String args[]){
      List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
        
      // Predicate<Integer> predicate = n -> true
      // n 是一个参数传递到 Predicate 接口的 test 方法
      // n 如果存在则 test 方法返回 true
        
      System.out.println("输出所有数据:");
        
      // 传递参数 n
      eval(list, n->true);
        
      // Predicate<Integer> predicate1 = n -> n%2 == 0
      // n 是一个参数传递到 Predicate 接口的 test 方法
      // 如果 n%2 为 0 test 方法返回 true
        
      System.out.println("输出所有偶数:");
      eval(list, n-> n%2 == 0 );
        
      // Predicate<Integer> predicate2 = n -> n > 3
      // n 是一个参数传递到 Predicate 接口的 test 方法
      // 如果 n 大于 3 test 方法返回 true
        
      System.out.println("输出大于 3 的所有数字:");
      eval(list, n-> n > 3 );
   }
    
   public static void eval(List<Integer> list, Predicate<Integer> predicate) {
      for(Integer n: list) {
        
         if(predicate.test(n)) {
            System.out.println(n + " ");
         }
      }
   }
}

相关文章

万字长文!呕心沥血整理的Java零基础入门到精通全套知识点教程

经常在知乎看到一个问题:Java学到什么程度可以算是精通?今天就围绕这个问题,其实学习 Java 学到什么程度算是精通,这个其实没有盖棺定论的,也不是说你拿个年薪几十万的 offer 就可以自诩精通了...

java学习:如何精通JAVA的25个标准

1.你需要精通面向对象分析与设计(OOA/OOD)、涉及模式(GOF,J2EEDP)以及综合模式。你应该了解UML,尤其是class,object,interaction以 及statediagram...

翻遍全网最详细java教程!从入门到精通!

亲爱的小伙伴们,今天要给大家分享一份堪称神仙级的 Java 入门教程!无论你是对编程一无所知的小白,还是渴望在编程领域更上一层楼的进阶者,这篇教程都将是你开启 Java 世界大门的金钥匙。一、为什么选...

精通 Java 是种怎样的体验?_如何才算精通java

Java,作为全球最流行的编程语言之一,一直以稳定、跨平台和面向对象的特性受到广大开发者的欢迎。作为一名精通Java的程序员,我享受着从Java编程中获取的深深满足感,同时也面临着自己的挑战和困扰。J...

必读!零基础学Java,快速入门到精通技巧

很多同学在刚接触Java的时候,会有些迷茫,不知道该从哪里入手,该学习掌握哪些必要的基础知识。小编总结了零基础学习Java编程语言的几个基础知识要点。希望对刚入门的Java新手有所帮助。先了解Java...

从入门到精通,超详细的程序员Java学习路线指南

说明最近也有很多人来向我"请教",他们大都是一些刚入门的新手,还不了解这个行业,也不知道从何学起,开始的时候非常迷茫,实在是每天回复很多人也很麻烦,所以在这里统一作个回复吧。Java学习路线当然,这里...