JDBC【封装工具类、SQL注入问题】

news2024/12/23 13:56:37

day54

JDBC

封装工具类01

创建配置文件

DBConfig.properties

driverName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/qnz01?characterEncoding=utf8&serverTimezone=UTC
username=root
password=root

新建配置文件,不用写后缀名
配置文件,不用写后缀

创建工具类

  1. 将变化的配置信息搬到配置文件中
  2. 工具类提供获取连接的方法
  3. 工具类提供关闭资源的方法
package com.qf.utils;

/**
 * 数据库工具类
 */
public class DBUtil {

    private static String url;
    private static String username;
    private static String password;

    static{
        //负责加载配置文件,只加载一次
        Properties properties = new Properties();
        try {
            properties.load(DBUtil.class.getClassLoader().getResourceAsStream("DBConfig.properties"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        String driverName = properties.getProperty("driverName");
        url = properties.getProperty("url");
        username = properties.getProperty("username");
        password = properties.getProperty("password");

        try {
            //加载驱动只加载一次
            Class.forName(driverName);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取连接对象
     */
    public static Connection getConnection() throws SQLException {
        Connection connection = DriverManager.getConnection(url,username,password);
        return connection;
    }

    /**
     * 关闭资源
     */
    public static void close(Connection connection, Statement statement, ResultSet resultSet){
        if(resultSet != null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if(statement != null){
            try {
                statement.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if(connection != null){
            try {
                connection.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }

}

理解

(1)新建配置文件
把导入驱动包和获取连接对象的参数url、root、password写入配置文件
(2)将获取连接对象方法封装到工具类DBUtil
01:加载配置文件
new 一个配置文件对象,配置文件对象调用加载方法加载进来【报异常先用try-catch】
之后就可以配置文件对象调用方法拿到配置信息【注意要与配置文件的名字相同,否则找不到就是null】
02:导入驱动包

03:在获取连接对象方法里,调用方法获取连接对象,返回连接对象

04:对于关闭资源,不用每次都写,直接在工具类写个关闭资源方法传入可能关的资源,里面非空判断哪些要关就行了

注意:

通常会把加载配置文件和导入驱动包放在静态代码块里,提高效率,不用获取一次连接加载一次,加载一次就行
对于重复使用的属性先静态定义【注意静态代码块只能初始化静态的属性】

基本操作报错try-catch【例如类这里的forName类未找到异常就要么资源文件没导入,要么配置文件对应的名称没写对】,对于需要对应不同业务处理异常就需要抛异常,在外面处理

现在对于先前的增删改查就不用那么麻烦,每个都写导入驱动包和获取连接对象的一大坨代码,当然这里就涉及封装工具类
直接用工具类调用获取连接对象的方法,关闭资源方法

测试类

对day53的增删改查进行进一步优化

package com.qf.jdbc01;

import com.qf.utils.DBUtil;
import org.junit.Test;

import java.sql.*;

public class Test01 {
    /**
     * 知识点:封装DBUtil - v1.0
     */

    //添加数据
    @Test
    public void test01() throws SQLException {
        //获取连接对象
        Connection connection = DBUtil.getConnection();

        //获取发送指令对象
        Statement statement = connection.createStatement();

        //发送SQL指令
        String sql = "INSERT INTO student(name,sex,age,salary,course) VALUES('柳如烟','女',28,6000,'HTML');";
        int num = statement.executeUpdate(sql);
        System.out.println("对于" + num + "行造成了影响");

        //关闭资源
        DBUtil.close(connection,statement,null);
    }


    //删除数据
    @Test
    public void test02() throws SQLException {
        //获取连接对象
        Connection connection = DBUtil.getConnection();

        //获取发送指令对象
        Statement statement = connection.createStatement();

        //发送SQL指令
        String sql = "DELETE FROM student WHERE id>10;";
        int num = statement.executeUpdate(sql);
        System.out.println("对于" + num + "行造成了影响");

        //关闭资源
        DBUtil.close(connection,statement,null);
    }

    //修改数据
    @Test
    public void test03() throws SQLException {

        //获取连接对象
        Connection connection = DBUtil.getConnection();

        //获取发送指令对象
        Statement statement = connection.createStatement();

        //发送SQL指令
        String sql = "UPDATE student SET age=21,salary=50000 WHERE id=3;";
        int num = statement.executeUpdate(sql);
        System.out.println("对于" + num + "行造成了影响");

        //关闭资源
        DBUtil.close(connection,statement,null);
    }

    //查询数据
    @Test
    public void test04() throws SQLException {

        //获取连接对象
        Connection connection = DBUtil.getConnection();

        //获取发送指令对象
        Statement statement = connection.createStatement();

        //发送SQL指令,并获取结果集对象
        String sql = "select * from student";
        ResultSet resultSet = statement.executeQuery(sql);

        //遍历结果集
        while(resultSet.next()){//判断是否有可迭代的数据行

            int id = resultSet.getInt("id");
            String name = resultSet.getString("name");
            String sex = resultSet.getString("sex");
            int age = resultSet.getInt("age");
            float salary = resultSet.getFloat("salary");
            String course = resultSet.getString("course");

            System.out.println(id + " -- " + name + " -- " + sex + " -- " + age + " -- " + salary + " -- " + course);
        }

        //关闭资源
        DBUtil.close(connection,statement,resultSet);
    }


}

SQL注入问题

理解

ps:select * from student where name=‘’ or 1=1 #’ and passwd=‘111111’;

当输入用户名和密码
’ or 1=1 # '【对于它分不清sql命令和数据,就会or 1=1判断为true,#后面理解为注释,就直接登录进去了】
user表

sql注入问题

package com.qf.jdbc02;

import com.qf.utils.DBUtil;

public class Test01 {
    /**
     * 知识点:SQL注入问题
     *
     * 出现原因:数据库分不清哪些是sql命令,哪些是数据
     *
     * 需求:模拟登录功能
     */
    public static void main(String[] args) throws SQLException {

        Connection connection = DBUtil.getConnection();

        Statement statement = connection.createStatement();

        Scanner scan = new Scanner(System.in);
        System.out.println("请输入账号:");
        String usernameVal = scan.nextLine();
        System.out.println("请输入密码:");
        String passwordVal = scan.nextLine();

        //select * from user where username='' or 1=1 #' and password='12312345'
        String sql = "select * from user where username='"+usernameVal+"' and password='"+passwordVal+"'";
        ResultSet resultSet = statement.executeQuery(sql);

        if(resultSet.next()){

            String username = resultSet.getString("username");
            String password = resultSet.getString("password");
            String nikeName = resultSet.getString("nike_name");

            System.out.println("登录成功");
            System.out.println(username);
            System.out.println(password);
            System.out.println(nikeName);
        }else{
            System.out.println("登录失败");
        }

        DBUtil.close(connection,statement,resultSet);
    }
}

解决

采用预编译对象来编程:告诉他,使用prepareStatement

sql命令中用?表示数据
注意:prepareStatement继承于statement
现在用prepareStatement拿到的sql是没有数据的,得到它的对象
再将输入的数据设置给这个对象,注意方法SetString的两个参数(下标,值)【注意下标从1开始】
sql注入问题解决

  1. 安全性,避免了SQL注入
  2. 性能,预编译,语句-编译-执行
package com.qf.jdbc02;

import com.qf.utils.DBUtil;

public class Test02 {
    /**
     * 知识点:SQL注入问题
     *
     * 出现原因:数据库分不清哪些是sql命令,哪些是数据
     * 解决方案:告诉数据库哪些是sql命令,哪些是数据
     *
     * 需求:模拟登录功能
     */
    public static void main(String[] args) throws SQLException {

        Connection connection = DBUtil.getConnection();

        String sql = "select * from user where username=? and password=?";
        PreparedStatement statement = connection.prepareStatement(sql);

        //输入数据后,将数据设置给statement对象
        Scanner scan = new Scanner(System.in);
        System.out.println("请输入账号:");
        String usernameVal = scan.nextLine();
        System.out.println("请输入密码:");
        String passwordVal = scan.nextLine();

        statement.setString(1,usernameVal);
        statement.setString(2,passwordVal);

        ResultSet resultSet = statement.executeQuery();

        if(resultSet.next()){

            String username = resultSet.getString("username");
            String password = resultSet.getString("password");
            String nikeName = resultSet.getString("nike_name");

            System.out.println("登录成功");
            System.out.println(username);
            System.out.println(password);
            System.out.println(nikeName);
        }else{
            System.out.println("登录失败");
        }

        DBUtil.close(connection,statement,resultSet);
    }
}

封装工具类02

更新数据(添加、删除、修改):

01方法传的参数(一个sql命令,另一个数据不确定用可变参数)
02由于不知道下标和值用setObject设置给statement,但有多个参数需要遍历处理就写个方法来专门处理【处理statement对象参数数据的处理器,注意setObject下标从1开始】直接调用处理
03添加操作返回影响行数
04而考虑到关闭资源,处理异常就不会关闭资源,对代码进行try-finally,考虑作用域把定义对象放在之外

添加–主键回填:

就是添加数据后,不再是返回影响的行数,加一个查询返回主键
主键回填理解:
主键回填

查询:

以前查询就打印出来结果,现在查询的数据封装成对象,不同表封装不同的对象
01学生类:有参无参自动生成时,window+alt+回车快速生成,生成时涉及属性选择ctrl+1全选
02方法返回值设置成list集合,类型用泛型;传的参数(哪个类的对象clazz【反射创建】,一个sql,一个可变参数)
03对于拿到的结果集,需要遍历, 一个数据创建一个对象
先泛型创建对象【无数据】,获取数据字段名才可以利用反射去设置对象的属性
怎么获取:先用结果集对象调用方法获取表数据对象,再表数据对象调用方法获取字段个数,再在遍历中通过字段个数循环获取字段名,字段值

反射获取类的属性对象和设置对象的属性【对于反射设置属性回顾以前反射讲到的方法】

04然后将封装的对象添加到对象集合再返回

05而考虑到关闭资源,处理异常就不会关闭资源,对代码进行try-finally,考虑作用域把定义对象放在之外

package com.qf.utils;

/**
 * 数据库工具类
 */
public class DBUtil {

    private static String url;
    private static String username;
    private static String password;

    static{
        //负责加载配置文件,只加载一次
        Properties properties = new Properties();
        try {
            properties.load(DBUtil.class.getClassLoader().getResourceAsStream("DBConfig.properties"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        String driverName = properties.getProperty("driverName");
        url = properties.getProperty("url");
        username = properties.getProperty("username");
        password = properties.getProperty("password");

        try {
            //加载驱动只加载一次
            Class.forName(driverName);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取连接对象
     */
    public static Connection getConnection() throws SQLException {
        Connection connection = DriverManager.getConnection(url,username,password);
        return connection;
    }

    /**
     * 关闭资源
     */
    public static void close(Connection connection, Statement statement, ResultSet resultSet){
        if(resultSet != null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if(statement != null){
            try {
                statement.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if(connection != null){
            try {
                connection.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 更新数据(添加、删除、修改)
     */
    public static int commonUpdate(String sql,Object... params) throws SQLException {
        Connection connection = null;
        PreparedStatement statement = null;
        try {
            connection = getConnection();
            statement = connection.prepareStatement(sql);
            paramHandler(statement,params);
            int num = statement.executeUpdate();
            return num;
        }finally {
            close(connection,statement,null);
        }
    }

    /**
     * 添加数据 - 主键回填(主键是int类型可以返回)
     */
    public static int commonInsert(String sql,Object... params) throws SQLException {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            connection = getConnection();
            statement = connection.prepareStatement(sql,PreparedStatement.RETURN_GENERATED_KEYS);
            paramHandler(statement,params);
            statement.executeUpdate();

            resultSet = statement.getGeneratedKeys();
            int primaryKey = 0;
            if(resultSet.next()){
                primaryKey = resultSet.getInt(1);
            }
            return primaryKey;
        }finally {
            close(connection,statement,resultSet);
        }
    }

    /**
     * 查询数据
     */
    public static <T> List<T> commonQuery(Class<T> clazz,String sql, Object... params) throws SQLException, InstantiationException, IllegalAccessException {

        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;

        try {
            connection = getConnection();
            statement = connection.prepareStatement(sql);
            paramHandler(statement,params);
            resultSet = statement.executeQuery();

            //获取表数据对象
            ResultSetMetaData metaData = resultSet.getMetaData();
            //获取字段个数
            int count = metaData.getColumnCount();

            List<T> list = new ArrayList<>();

            while(resultSet.next()){

                T t = clazz.newInstance();

                //获取字段名及数据
                for (int i = 1; i <= count; i++) {
                    String fieldName = metaData.getColumnName(i);
                    Object fieldVal = resultSet.getObject(fieldName);
                    setField(t,fieldName,fieldVal);
                }
                list.add(t);
            }
            return list;
        } finally {
            DBUtil.close(connection,statement,resultSet);
        }
    }

    /**
     * 处理statement对象参数数据的处理器
     */
    private static void paramHandler(PreparedStatement statement,Object... params) throws SQLException {
        for (int i = 0; i < params.length; i++) {
            statement.setObject(i+1,params[i]);
        }
    }

    /**
     * 获取当前类及其父类的属性对象
     * @param clazz class对象
     * @param name 属性名
     * @return 属性对象
     */
    private static Field getField(Class<?> clazz,String name){

        for(Class<?> c = clazz;c != null;c = c.getSuperclass()){
            try {
                Field field = c.getDeclaredField(name);
                return field;
            } catch (NoSuchFieldException e) {
            } catch (SecurityException e) {
            }
        }
        return null;
    }

    /**
     * 设置对象中的属性
     * @param obj 对象
     * @param name 属性名
     * @param value 属性值
     */
    private static void setField(Object obj,String name,Object value){

        Field field = getField(obj.getClass(), name);
        if(field != null){
            field.setAccessible(true);
            try {
                field.set(obj, value);
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
}

调用工具类的方法实现增删改查

package com.qf.jdbc03;

import com.qf.utils.DBUtil;

public class Test01 {
    /**
     * 知识点:封装数据库 - v2.0
     */

    //添加数据
    @Test
    public void test01() throws SQLException {
        String sql = "INSERT INTO student(name,sex,age,salary,course) VALUES(?,?,?,?,?)";
        int num = DBUtil.commonUpdate(sql, "天使萌", "女", 23, 10000, "HTML");
        System.out.println("对于" + num + "行造成了影响");
    }

    //删除数据
    @Test
    public void test02() throws SQLException {
        String sql = "delete from student where id=?";
        int num = DBUtil.commonUpdate(sql, 3);
        System.out.println("对于" + num + "行造成了影响");
    }

    //修改数据
    @Test
    public void test03() throws SQLException {
        String sql = "update student set salary=? where id=?";
        int num = DBUtil.commonUpdate(sql, 15000,1);
        System.out.println("对于" + num + "行造成了影响");
    }

    //添加数据 - 主键回填
    @Test
    public void test04() throws SQLException {
        String sql = "INSERT INTO student(name,sex,age,salary,course) VALUES(?,?,?,?,?)";
        int primaryKey = DBUtil.commonInsert(sql, "铃原爱蜜莉", "女", 23, 10000, "HTML");
        System.out.println("返回的主键是:" + primaryKey);
    }

    //查询数据
    @Test
    public void test05() throws SQLException, InstantiationException, IllegalAccessException {
        String sql = "select * from student where id<?";
        List<Student> list = DBUtil.commonQuery(Student.class, sql, 8);
        for (Student stu : list) {
            System.out.println(stu);
        }
    }
}

学生类

package com.qf.jdbc03;

public class Student {

    private int id;
    private String name;
    private String sex;
    private int age;
    private float salary;
    private String course;

    public Student() {
    }

    public Student(int id, String name, String sex, int age, float salary, String course) {
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.age = age;
        this.salary = salary;
        this.course = course;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public float getSalary() {
        return salary;
    }

    public void setSalary(float salary) {
        this.salary = salary;
    }

    public String getCourse() {
        return course;
    }

    public void setCourse(String course) {
        this.course = course;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", age=" + age +
                ", salary=" + salary +
                ", course='" + course + '\'' +
                '}';
    }
}

小结:

JDBC:封装工具类,对增删改查优化,SQL注入问题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1888461.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux】TCP协议【下三】{面向字节流/粘包问题/TCP异常情况/文件和Socket}

文章目录 7.面向字节流TCP&#xff08;传输控制协议&#xff09;和UDP&#xff08;用户数据报协议&#xff09; 8.粘包问题9.TCP异常情况10.再谈文件和socket的关系 7.面向字节流 创建一个TCP的socket, 同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区;一个链接一对发收缓…

day09了 加油

浅拷贝 指向同一个地址空间 右边不可取地址 左边一定是到了具体的位置 右值引用std&#xff1a;&#xff1a; move 相信大家默认构造函数都没有问题&#xff0c;所以就不贴例子了 浅拷贝构造函数 只负责复制地址&#xff0c;而不是真的把完整的内存给它 #include <iostre…

Qt——升级系列(Level Seven):事件、文件

目录 Qt事件 事件介绍 事件的处理 按键事件 鼠标事件 定时器 事件分发器 事件过滤器 Qt文件 Qt文件概述 输入输出设备类 文件读写类 文件和目录信息类 Qt事件 事件介绍 事件是应⽤程序内部或者外部产⽣的事情或者动作的统称。在 Qt 中使⽤⼀个对象来表⽰⼀个事件。所有的 Qt …

Linux修炼之路之进程概念,fork函数,进程状态

目录 一&#xff1a;进程概念 二&#xff1a;Linux中的进程概念 三&#xff1a;用getpid(),getppid()获取该进程的PID,PPID 四&#xff1a;用fork()来创建子进程 五&#xff1a;操作系统学科的进程状态 六&#xff1a;Linux中的进程状态 接下来的日子会顺顺利利&#xf…

WordPress网站添加插件和主题时潜在危险分析

WordPress 最初只是一个简单的博客软件&#xff0c;现在据估计为全球前 1000 万个网站中的 30% 提供支持。WordPress受欢迎的因素之一是可以轻松创建插件和主题来扩展它并提供比默认设置更多的功能。 目前&#xff0c;WordPress 网站列出了 56,000 多个插件以及数千个主题。插件…

提升用户体验之requestAnimationFrame实现前端动画

requestAnimationFrame是什么 MDN官方解释 解析这段话&#xff1a; 1、那么浏览器重绘是指什么呢&#xff1f; ——大多数电脑的显示器刷新频率是60Hz&#xff0c;1000ms/6016.66666667ms的时间刷新一次 2、重绘之前调用指定的回调函数更新动画&#xff1f; ——requestAnima…

机器学习辅助的乙醇浓度检测

目录 1.为什么要机器学习 2. 神经网络一般组成 3.BP神经网络工作过程 4.评价指标 5.实操代码 1.为什么要用机器学习 人工分析大量的谐振模式&#xff0c;建立各种WGM的响应与未知目标之间的关系&#xff0c;是一个很大的挑战。机器学习(ML)能够自行识别全谱的全部特征。作为…

Python深度理解系列之【排序算法——冒泡排序】

读者大大们好呀&#xff01;&#xff01;!☀️☀️☀️ &#x1f440;期待大大的关注哦❗️❗️❗️ &#x1f680;欢迎收看我的主页文章➡️木道寻的主页 文章目录 &#x1f525;前言&#x1f680;冒泡排序python实现算法实现图形化算法展示 ⭐️⭐️⭐️总结 &#x1f525;前…

ONLYOFFICE8.1版本桌面编辑器简单测评

ONLYOFFICE官网链接&#xff1a;在线PDF查看器和转换器 | ONLYOFFICE ONLYOFFICE介绍&#xff1a;https://www.onlyoffice.com/zh/office-suite.aspx OnlyOffice 是一款免费且开源的 Office 协作办公套件&#xff0c;支持桌面端和移动端等多平台&#xff0c;由一家领先的 IT 公…

OpenStack开源虚拟化平台(二)

目录 三、对象存储服务Swift&#xff08;一&#xff09;Swift特性&#xff08;二&#xff09;应用场景&#xff08;三&#xff09;Swift主要组件&#xff08;四&#xff09;Swift基本原理&#xff08;五&#xff09;实例分析 四、镜像服务Glance&#xff08;一&#xff09;Glan…

如何在 Java 应用中使用 Jedis 客户端库来实现 Redis 缓存的基本操作

本人详解 作者:王文峰,参加过 CSDN 2020年度博客之星,《Java王大师王天师》 公众号:JAVA开发王大师,专注于天道酬勤的 Java 开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯 山峯 转载说明:务必注明来源(注明:作者:王文峰…

图解 Kafka 架构

写在前面 Kafka 是一个可横向扩展&#xff0c;高可靠的实时消息中间件&#xff0c;常用于服务解耦、流量削峰。 好像是 LinkedIn 团队开发的&#xff0c;后面捐赠给apache基金会了。 kafka 总体架构图 Producer&#xff1a;生产者&#xff0c;消息的产生者&#xff0c;是消息的…

不改代码,实现web.config或app.config的连接字符串加密解密

目的&#xff1a;加密字符串&#xff0c;防止明文显示。 好处&#xff1a;不用修改代码&#xff0c;微软自带功能&#xff0c;自动解密。 web.config 参考相关文章&#xff1a; Walkthrough: Encrypting Configuration Information Using Protected Configuration | Microso…

本地通过ollama下载模型,并使用python跑这个本地模型

1&#xff0c;这是ollama地址&#xff0c;下载对应的安装包 https://ollama.com/?viaurlainavpro.com 2&#xff0c;下载完直接安装即可&#xff0c;安装完后&#xff0c;winr打开cmd&#xff0c;出现这个&#xff0c;基本就妥了 3&#xff0c;这里我们需要去下载模型&#x…

笔记:Git学习之应用场景和使用经验

目标&#xff1a;整理Git工具的应用场景和使用经验 一、开发环境 Git是代码版本控制工具&#xff1b;Github是代码托管平台。 工具组合&#xff1a;VSCode Git 需要安装的软件&#xff1a;vscode、Git 其中vscode需要安装的插件&#xff1a;GitLens、Git History 二、应用…

【NLP学习笔记】load_dataset加载数据

除了常见的load_dataset(<hf上的dataset名>)这种方式加载HF上的所有数据外&#xff0c;还有其他custom的选项。 加载HF上部分数据 from datasets import load_dataset c4_subset load_dataset("allenai/c4", data_files"en/c4-train.0000*-of-01024.js…

PowerBi 获取指定时间间隔的日期的方法

获取指定时间间隔的日期&#xff0c;比如我们想得到2024年5月31日后的第三天。 网络上的教程一般是使用DATEADD()函数。 但是这个函数返回的是表。假如我们的需求是不做汇总等计算&#xff0c;只是把它作为一个计算列&#xff0c;或者度量值&#xff0c;那么我更推荐用DATE(&…

webstorm 高效查看不同分支差异 摒弃你的git diff手动操作

背景 每次代码冲突或者版本发生异常时&#xff0c;排查不同版本时就是一个头大的问题&#xff0c;头大的点在于用 vscode 的 git diff 一点点地排查和比较&#xff0c;耗时耗力&#xff0c;版面展不开&#xff0c;commit 差异看不出来&#xff0c;每个页面的代码不同也不能快速…

【Java08】方法(上)

从面向对象的角度来看&#xff0c;Java中的方法是类或对象行为的抽象。但和C不同&#xff0c;Java中”剔除“了C中”残留“的结构化编程。具体地说&#xff0c;Java中的方法必须在类中定义&#xff0c;没有独立存在的函数。 也有人把结构化编程&#xff0c;叫做”面向函数的编程…