武功滞涩处Java篇
Nico Liao 阿蒙

文章格式提醒:问题2级、答案分支依次递减;未解决的结尾用”?”进行标识;倒序更新。

主键策略?

持久层,业务层?

Mybatis的作用及原理?

Java的动态代理机制?

回滚

定义

****回滚(rollback)****就是在事务提交之前将数据库数据恢复到事务修改之前数据库数据状态。为了保证在应用程序、数据库或系统出现错误后,数据库能够被还原,以保证数据库的完整性,所以需要进行回滚。

例如,用户A给用户B转账,在数据库中就需要给A与B的账户信息进行修改(update)操作,而这两条sql语句必须都执行或者都不执行。 例如先执行用户B的修改(update)语句,使用户B的账户金额增加了1000,然后执行用户A的update语句,使用户A的账户金额减少了1000。 如果用户A的账户金额大于1000,则交易顺利进行,不存在任何问题,但是当用户A的账户金额小于1000时,由于转账金额不允许大于账户金额,第二条sql语句语句就无法正确执行,此时,数据库的状态必须回到没有执行B的update语句之前,需要进行回滚操作,回滚就是执行一遍相反的操作,此时再执行B的update金额减1000。

回滚与撤销的区别:

回滚是指将数据库的状态恢复到执行事务之前的状态,其中可能会使用UNDO日志进行回滚。

撤销是一种记录日志的方式,并不是主要服务于事务回滚,而是主要用于系统从故障中恢复。 例如,系统突然断电,系统要根据UNDO日志对未完成的事务进行处理,保证数据库的状态为执行这些事务之前的状态。

回滚的作用就是,当有一个SQL语句执行时,条件不符合要求,比如你要插入一个数据,但是插入的数据要有条件的,这时候你就可以用回滚,如果条件成功就COMMIT提交的意思,不然就ROLLBACK回滚,也就是说插入不成功
原文链接

Beanfactory接口?

ApplicationContext

Spring常用注解?

@Autowired / @Test /

@service

@Transactional

连接池的定义原理等

转自王杰的博客一天天的博客

定义

数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;

释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。

———以上参考《百度百科》

必要性

建立一个数据库连接是一件非常耗时(消耗时间)耗力(消耗资源)的事情。之所以会这样,是因为连接到数据库服务器需要经历几个漫长的过程:建立物理通道(例如套接字或命名管道),与服务器进行初次握手,分析连接字符串信息,由服务器对连接进行身份验证,运行检查以便在当前事务中登记等等。

连接池就是这样一个容器:它存放了一定数量的与数据库服务器的物理连接。因此,当我们需要连接数据库服务器的时候,只需去池(容器)中取出一条空闲的连接,而不是新建一条连接。这样的话,我们就可以大大减少连接数据库的开销,从而提高了应用程序的性能。

工作原理

连接池的工作原理主要由三部分组成,分别为

  • 连接池的建立
  • 连接池中连接的使用管理
  • 连接池的关闭

第一、连接池的建立。

  一般在系统初始化时,连接池会根据系统配置建立,并在池中创建了几个连接对象,以便使用时能从连接池中获取。连接池中的连接不能随意创建和关闭,这样避免了连接随意建立和关闭造成的系统开销。

Java中提供了很多容器类可以方便的构建连接池,例如Vector、Stack等。

第二、连接池的管理。

  连接池管理策略是连接池机制的核心,连接池内连接的分配和释放对系统的性能有很大的影响。其管理策略是:

当客户请求数据库连接时,

1)如果池中有空闲连接可用,返回该连接。
2)如果没有空闲连接,池中连接都已用完,创建一个新连接添加到池中。
3)如果池中连接已达到最大连接数,请求按设定的最大等待时间进入等待队列直到有空闲连接可用。
4)如果超出最大等待时间,则抛出异常给客户。
当客户释放数据库连接时,先判断该连接的引用次数是否超过了规定值,如果超过就从连接池中删除该连接,否则保留为其他客户服务。

该策略保证了数据库连接的有效复用,避免频繁的建立、释放连接所带来的系统资源开销。

如果连接长时间空闲,或检测到与服务器的连接已断开,连接池管理器也会将该连接从池中移除。

第三、连接池的关闭。

当应用程序退出时,关闭连接池中所有的连接,释放连接池相关的资源,该过程正好与创建相反。

注意点

1、并发问题

为了使连接管理服务具有最大的通用性,必须考虑多线程环境,即并发问题。

这个问题相对比较好解决,因为各个语言自身提供了对并发管理的支持像java,c#等等,使用synchronized(java)lock(C#)关键字即可确保线程是同步的。

2、事务处理

我们知道,事务具有原子性,此时要求对数据库的操作符合“ALL-OR-NOTHING”原则,即对于一组SQL语句要么全做,要么全不做。

我们知道当2个线程共用一个连接Connection对象,而且各自都有自己的事务要处理时候,对于连接池是一个很头疼的问题,因为即使Connection类提供了相应的事务支持,可是我们仍然不能确定那个数据库操作是对应那个事务的,这是由于我们有2个线程都在进行事务操作而引起的。

为此我们可以使用每一个事务独占一个连接来实现,虽然这种方法有点浪费连接池资源但是可以大大降低事务管理的复杂性。

3、连接池的分配与释放

连接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,可以提高连接的复用度,从而降低建立新连接的开销,同时还可以加快用户的访问速度。

对于连接的管理可使用一个List。即把已经创建的连接都放入List中去统一管理。每当用户请求一个连接时,系统检查这个List中有没有可以分配的连接。如果有就把那个最合适的连接分配给他,如果没有就抛出一个异常给用户。

4、连接池的配置与维护

连接池中到底应该放置多少连接,才能使系统的性能最佳?

系统可采取设置最小连接数(minConnection)和最大连接数(maxConnection)等参数来控制连接池中的连接。

比方说,最小连接数是系统启动时连接池所创建的连接数。如果创建过多,则系统启动就慢,但创建后系统的响应速度会很快;如果创建过少,则系统启动的很快,响应起来却慢。这样,可以在开发时,设置较小的最小连接数,开发起来会快,而在系统实际使用时设置较大的,因为这样对访问客户来说速度会快些。

最大连接数是连接池中允许连接的最大数目,具体设置多少,要看系统的访问量,可通过软件需求上得到。

如何确保连接池中的最小连接数呢?

有动态和静态两种策略:

  • 动态即每隔一定时间就对连接池进行检测,如果发现连接数量小于最小连接数,则补充相应数量的新连接,以保证连接池的正常运转。
  • 静态是发现空闲连接不够时再去检查。

通过jdbcTemplat配置数据库连接池

最大的空闲时间:数据库连接对象所能存活的最大时间。

最小连接数:是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费。

最大连接数:所能创建连接池的最大数目,如果数据库连接池连接请求超过该数目,这个请求就先等待。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!--配置文件-->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">

<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/test"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
<property name="idleTimeout" value="60000"></property><!--最大空闲时间-->
<property name="maximumPoolSize" value="20"></property><!--最大连接数-->
<property name="minimumIdle" value="10"></property><!-- 最小连接数 -->
</bean>

<bean id="jdbcTemplate " class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<context:component-scan base-package="com.jd"></context:component-scan>
</beans>

Hash是个什么东东

定义

Hash一般翻译为散列,还有音译为哈希,本文我们统称为哈希(这么叫好听,哈希=散列),通过百度以及谷歌都没有直接找到Hash的定义,而是找到了一些相关的概念,哈希算法,哈希函数,哈希表等概念。

我所理解的哈希是指一个过程,这个过程就是把任意长度的输入,通过哈希算法,变换成固定长度的输出,所输出的称为哈希值。这种变换是一种压缩映射,也即哈希值所占的空间一般来说远小于输入值的空间,不同的输入可能会哈希出相同的输出(概率很小)。

哈希函数、算法

哈希算法将任意长度的二进制值映射为较短的固定长度的二进制值,这个小的二进制值称为哈希值。哈希值是一段数据唯一且极其紧凑的数值表示形式。如果散列一段明文而且哪怕只更改该段落的一个字母,随后的哈希都将产生不同的值。要找到散列为同一个值的两个不同的输入,在计算上是不可能的,所以数据的哈希值可以检验数据的完整性。一般用于快速查找和加密算法 —《数据结构与算法分析》

哈希表

散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

特点

如果两个哈希值是不相同的(根据同一函数),那么这两个散列值的原始输入一定是不相同的。
如果两个哈希值相同,两个输入值很可能(极大概率)是相同的,但也可能不同,这种情况称为“哈希碰撞”
抗篡改能力:对于一个数据块,哪怕只改动其一个比特位,其hash值的改动也会非常大。
它是一种单向函数是“非对称”的,即它是一个从明文到密文的不可逆的映射,只有加密过程,没有解密过程。

部分引自:博文一博文二 ,更多请参考知乎问答

常见的设计模式?

无法从static方法中引用非static方法

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.company;
import jdk.internal.util.xml.impl.Input;

import java.util.Scanner;
public class CalculatePension {
public static void main(String[] args) {
// read inputs
Scanner in = new Scanner(System.in);
System.out.print("How much money do you need to retire? ");
double goal = in.nextDouble();
System.out.print("How much money will you contribute every year? ");
double payment = in.nextDouble();
System.out.print("Interest rate in %: ");
double interestRate = in.nextDouble();
double balance = 0;
int year = 0;

do {
balance += payment;
double interest = balance * interestRate / 100;
balance += interest;
year++;
// print current balance
// ask if ready to retire and get input
}
while (Input.equals("N"));
}
}

报错:“java: 无法从静态上下文中引用非静态方法 equals(java.lang.Object)”

心得体会:

  • 静态方法又叫做类方法,不需要实例化而是通过类名称来进行调用,与实例无关;非静态方法又叫做实例对象方法,必须通过对方访问的方法;
  • 解决方案:
    • 在外部定义了动态类而导致如此报错的,可以将该动态类改为静态类,即static class
    • 或者可在main主方法里实例化非静态方法的对象,再对非静态方法进行调用

生成随机数有几种方法

1.根据强制类型转换来实现:

1
int r=(int)(Math.random()*n);

2.直接调用随机数生成方法

类的访问器和修改器方法?

具体如何使用GregorianCanlendar对象?

IDEA的调试技巧

步过和步入

img

强行步入,步出

img

回退,和运行到光标处

运行到光标处:光标点到哪里,他的断点执行到哪一行

在这里插入图片描述

计算表达式:可以改变变量的值

在这里插入图片描述

条件断点: for循环

当我们想查看for循环中i=300的值,一个个点是不可能的,此时需要条件断点。

在这里插入图片描述

在这里插入图片描述

放行

在这里插入图片描述

多线程调试

通配符

通配符是一种特殊语句,主要有星号(*)和问号(?),用来模糊搜索文件。当查找文件夹时,可以使用它来代替一个或多个真正字符;当不知道真正字符或者懒得输入完整名字时,常常使用通配符代替一个或多个真正的字符。

星号(*)

可以使用星号代替零个、单个或多个字符。如果正在查找以AEW开头的一个文件,但不记得文件名其余部分,可以输入AEW*,查找以AEW开头的所有文件类型的文件,如AEWT.txt、AEWU.EXE、AEWI.dll等。

要缩小范围可以输入AEW*.txt,查找以AEW开头的所有文件类型并.txt为扩展名的文件如AEWIP.txt、AEWDF.txt。

2、问号(?)

可以使用问号代替一个字符。如果输入love?,查找以love开头的一个字符结尾文件类型的文件,如lovey、lovei等。要缩小范围可以输入love?.doc,查找以love开头的一个字符结尾文件类型并.doc为扩展名的文件如lovey.doc、loveh.doc。

img

扩展资料

通配符使用方法——模糊条件求和:

1、例如要求:求出商品中包含“T恤”的总数量

需要在项目栏中求和出包含T恤的总数量,输入公式:=SUMIF(A2:A18,”T恤“,D2:D18) 因为我们不能确定“T恤”的前后有没有其他数据,所以条件为”T恤“,则可以把所有包含“T恤”的数量求出。

2、例如要求:求出商品中最后一个字是”裙”,且单元格是5个字符对应的总数量

在项目栏中输入公式:=SUMIF(A2:A18,“????裙”,D2:D18),因为必须是5个字符且最后一个是“裙”,所以条件输入“????裙”1个“?”代表任意1个字符。

从键盘接受输入的字符

  • 第一种方法:通过接收字符串 再接收其第一个字符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import java.util.Scanner;    //导包
    class T1{
    public static void main(String[] args){
    Scanner sc = new Scanner(System.in);
    System.out.println("请输入字符或字符串");
    char c =sc.next().charAt(0); //只接收字符串的第一个字符
    System.out.println(c);
    }
    }
  • 第二种方法: 同样可以实现录入一个字符的功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import java.util.*;  
    public class Test_01
    {
    public static void main(String[] args)throws Exception
    {
    System.out.println("请输入一个字符");
    char c=(char)System.in.read();
    System.out.println(c);
    }
    }
  • 第三种方法:利用类库中的Scanner类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Scanner in = new Scanner(System.in);  //定义一个Scanner类对象,用来接收数据

    System.out.print("Please input a string :");

    Int a = in.nextInt(); //把接收的整形数据放到a中

    String str = in.nextLine(); //把输入的当前行数据放到字符串变量str中

    Char arr[] = str.toCharArray(); //字符串转换成数组

    String st = arr.toString(); //字符数组转换成字符串
  • 第四种方法:字符输入流InputStreamReader

    1
    2
    3
    4
    5
    InputStreamReader reader = new InputStreamReader(System.in);    //定义一个字符输入流对象reader

    BufferedReader input = new BufferedReader(reader); //把reader接收的字符放入到缓冲区input中
    System.out.print("Please enter your word:");
    String text = input.readLine(); //从input缓冲区中读取一行字符
  • 第五种方法:数据输入流:DataStreamReader

    1
    2
    3
    4
    5
    DataInputStream  in = new DataInputStream(System.in);   //定义一个数据输入流对象in,从键盘接收数据

    System.out.println("Please input a number:");//键盘读入的是数字类型,不是字符、字符串类型

    Int i = in.readInt(); //读取一个整形数据放到i中

i++和++i异同与作用

记得刚开始学编程的时候还是从c语言开始的,还是看的谭浩强写的那本书,上面对介绍i++和++i的区别如下:
i++是先赋值,然后再自增;++i是先自增,后赋值。
用代码表示就是:
若 a = i++; 则等价于 a=i;i=i+1;
而 a = ++i; 则等价于 i=i+1;a=i;

在使用i=i++的过程中,它会先把i的原始值0复制到操作数栈中,然后再对局部变量表中的0进行+1操作使得i变为了1,此时操作数栈顶的值为0,然后执行赋值操作时候使用的是弹出的操作数栈顶的值,所以最后i又被修改为了0;
而i=++i的过程则是先对局部变量表中i的原始值进行加1的操作,即使得i由0变为1,然后将i的值复制到操作数栈,最后赋值即弹出操作数栈顶的值。
i++;和++i;的执行过程和结果是一样的。
在使用i++和++i赋值的过程中,他们区别在于前者先复制当前数据,再进行原始值加1的操作,后者则先进行了原始值加1的操作,再对计算后的结果进行了复制,最后返回的其实都是放入操作数栈的拷贝。
看懂了上面的原理,你应该能明白为什么int i = 0;i=i++ + ++i;等于2了吧。如果按原来的定义取理解,也许会得出结果为1。

资源绑定器

在一些需要配置较多参数(url,user,password等)的项目中可以采用此种方式,省力

  1. src目录下新建一个resources的包

  2. resources包下面新建一个db.propertiesfile

  3. db.properties的文件中填写相应的配置内容

    image-20220307205447231

  4. 在项目代码中写上如下代码,以完成资源的绑定

1
2
3
4
5
6
7
8
9
class ResourceBundleTest{
public static void main(String[] args) {
// 资源绑定器,只能绑定xxx.properties文件。并且这个文件必须在类路径下。文件扩展名也必须是properties, 并且在写路径的时候,路径后面的扩展名不能写。
// ResourceBundle bundle = ResourceBundle.getBundle("reflectClassInfo2");
ResourceBundle bundle = ResourceBundle.getBundle("javase/reflectBean/db");
String className = bundle.getString("className");
System.out.println(className);
}
}

如何将一个变量传到一个字符串里面呢

例如如何将loginnameloginpwd传输到下面这条语句的合适位置呢?

1
String sql = "select * from t_user where login_name = '' and login-pwd = ";

方法:直接用双引号将字符串包起来,再用+将变量连起来

String sql = "select * from t_user where login_name = '"+ loginname +"' and login-pwd ='"+ loginpwd +""' ";

SQL注入现象

如在判断登录的底层代码中存在如下语句

String sql = "select * from t_user where login_name = '"+ loginname +"' and login-pwd ='"+ loginpwd +""' ";

登录时输入信息为

1
2
用户名:fsda
密码:fsda' or '1'='1

此时执行登录操作之后也能成功

  • 在用户登录时用户输入的用户名和密码信息中含有SQL语句的关键字,这些关键字和数据库底层的SQL语句进行了拼接,导致运行时原底层的SQL语句的含义被扭曲了,也能正常运行蒙混过了判断条件。

  • 根本原因:使用了Statement对字符串进行了拼接,用户输入的信息参与了底层SQL语句的编译

    java.sql.Statement接口的特点:先进行了字符串的拼接。然后再进行sql语句的编译

    优点:使用Statement可以进行sql语句的拼接

    缺点:因为拼接的存在,导致可能给不法分子机会,导致SQL注入。

    java.sql.PreparedStatement接口的特点:先进行SQL语句的编译,然后再进行SQL语句的传值

    优点:避免SQL注入

    缺点:没办法进行SQL 语句的拼接,只能给SQL语句传值。

  • 解决办法:不再使用Statement方法,改用java.sql.PreparedStatement

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //一个问号表示一个占位符,一个占位符只能接受一个”一个值/一个数据“
    String sql = "select * from t_user where login_namne = ? and login_pwd =?";
    stmt.conn.prepareStatement(sql);//将sql语句发送给DBMS,进行sql语句的编译

    //给占位符传值,JDBC所有的下标都是从一开始的;
    stmt.setString(1,loginName);
    stmt.setString(2,loginPwd);

    //执行SQL
    //这个方法不需要将sql语句传递金曲,不能这样:rs=. stmt.executeQuery(sql);
    rs = stmt.executeQuery();

静态代码块?

类加载?

悲观锁?乐观锁?

args[]?

var

Java中var是Java10版本新出的特性,用它来定义局部变量。

使用var 定义变量的语法:

var 变量名 = 初始值;

如果代码:
var a = 20;
var a =8.9;
这样的代码会报错 显示int到double的转换;
Java是强类型语言,每个变量都有固定的变量类型。

var是什么:

var不是关键字,它相当于是一种动态类型;
var动态类型是编译器根据变量所赋的值来推断类型;
var 没有改变Java的本质,var只是一种简便的写法,
就是说在定义局部变量时,任意什么类型都可以用var定义变量的类型会根据所赋的值来判断。

用var声明变量的注意事项:

1,var只能在方法内定义变量,不允许定义类的成员变量。
2,var 定义变量必须赋初始值,——》以后不能在赋初始值。
3,var每次只能定义一个变量,不能复合声明变量。

使用var定义变量的优缺点:

优点:使代码简洁和整齐。
缺点:降低了程序的可读性。
什么时候该用var定义变量:
如果你定义变量时,给变量赋给一个直观的值,这时就可以使用var定义变量,

最不能使用var定义变量:

1,给var定义的变量赋给一个很复杂的表达式时,这样使表达式的返回值不直观,不能用var定义变量。
2,var定义的变量作用域很长时,方法长和var变量影响较大时,不用var定义变量。
原文链接

Spring注解的原理

Spring框架的核心就是IOC,通过controller一类注解的bean的实例化过程可以大体总结spring注解的工作原理:

  • 利用asm技术扫描class文件,转化成Springbean结构,把符合扫描规则的(主要是是否有相关的注解标注,例如@Component)bean注册到Spring 容器中beanFactory
  • 注册处理器,包括注解处理器
  • 实例化处理器(包括注解处理器),并将其注册到容器的beanPostProcessors列表中
  • 创建bean的过程中,属性注入或者初始化bean时会调用对应的注解处理器进行处理。

接口有什么用?

篇一:

下面我给大家总结了4点关于JAVA中接口存在的意义:

  1、重要性:在Java语言中, abstract class 和interface 是支持抽象类定义的两种机制。正是由于这两种机制的存在,才赋予了Java强大的 面向对象能力。

  2、简单、规范性:如果一个项目比较庞大,那么就需要一个能理清所有业务的架构师来定义一些主要的接口,这些接口不仅告诉开发人员你需要实现那些业务,而且也将命名规范限制住了(防止一些开发人员随便命名导致别的程序员无法看明白)。

  3、维护、拓展性:比如你要做一个画板程序,其中里面有一个面板类,主要负责绘画功能,然后你就这样定义了这个类。

​ 可是在不久将来,你突然发现这个类满足不了你了,然后你又要重新设计这个类,更糟糕是你可能要放弃这个类,那么其他地方可能有引用他,这样修改起来很麻烦。

​ 如果你一开始定义一个接口,把绘制功能放在接口里,然后定义类时实现这个接口,然后你只要用这个接口去引用实现它的类就行了,以后要换的话只不过是引用另一个类而已,这样就达到维护、拓展的方便性。

  4、安全、严密性:接口是实现软件松耦合的重要手段,它描叙了系统对外的所有服务,而不涉及任何具体的实现细节。这样就比较安全、严密一些(一般软件服务商考虑的比较多)。

篇二:

接口是软件工程最重要的概念,在java上要格外感谢Spring的贡献。这个概念对于新人来讲,是比较难理解的,最重要的原因就是需要有一定的代码量,特别是做过一些项目的重构,维护,变更等事情的时候感触才会更深一些。

1 “接口+实现”最常见的优势就是实现类和接口分离,在更换实现类的时候,不用更换接口功能。

比较常见的例子是发送短信。一般发送短信的场景包括注册用户,找回密码,重要通知,修改交易密码等。短信现在的结构是先接上各家短信通道公司,再经由联通移动等发送出去。

一般而言,短信分成两种,一种注册短信,一次只发给用户一条。这种短信到达率比较高,可能会在99%以上,也要看各种短信通道方,更会区分移动和联通。另外一种是营销短信,这种短信常见于“某公司3周年大庆,1元领取程序员鼓励师”之类的。这种短信到达率非常低。而且也经常会被封掉。但是短信又是注册的第一步,用户体验做的再好,手机收不到验证码也没用。所以觉见的做法是,会备用两个或者是多个短信通道

刚刚已经讲过了,调用短信接口的地方比较多,可能是用户发起的,也可能是程序检测到某种行为触发的。也就是说,会有多个地方调用短信接口,那么我们这个时候要解决的问题就是,能否在更换短信通道方的时候,不更改其他模块中被引入的代码?接口在这个时候就完美的实现了这个功能点。无论是哪个模块,我要发送的内容和预期的结果是一致的,具体是用哪家短信通道的实现类,不重要。

所以通常是一个SMSService做为接口,不同的公司因为有不同的实现方式,所以会有多个实现类,比如说SMSService{CorpA}Impl,SMSService{CorpB}Impl。这是一个完美的抽象,无论未来有多少种短信公司接入,无论短信公司的营销人员送了多少个香吻给公司的商务总监,程序员总是能够开心的完成功能。

2.这对于做单元测试也非常有帮助。

如果你是一个有了那么点经验的程序员,如果你还没有习惯TDD的开发。可以体验一下这种写法。还是拿短信为例。

先写一个SMSServiceTest。然后写一个Test方法。这个时候什么都没有,不用管。先直接这么写。

int code=SMSSevice.sendTextMessage(mobile,content,type);

这个时候IDE会提示你没有这个SMSService,用代码自动生成工具去创建这么一个接口出来。再根据提示把方法创建出来。

再写 SMSService smsService=new SMSServiceCorpaImpl();

再根据代码把实现类生成了。一般来说IDE会自动留一个空的方法。不用管。

这里只是一个简单的例子,但是你发现,当你用TDD的这种方式去写代码的时候,完全不用关系SMSService是怎么内部实现的。你只需要继续写你的单元测试代码好了,明确的知道这个SMSService要做的功能是发送短信,需要传递手机号,内容,类型,返回一个状态码。

那么接着说为什么对单元测试很方便?一般而言会用Spring配置Bean,所以实际上你的单元测试代码也不用有改动,无论是测试哪一个实现类,都只通过更改配置文件就可以完成。

想想,如果没有接口呢?是不是要对每一个短信通道单独写一个单元测试的方法?

3.对于不需要频繁更变实现类的方法,是不是就可以不用写接口了?

答案是No。整个系统架构的代码可以单纯认为有四部分构成。Model+Interface+Service+Util

Model是纯粹的Pojo,贫血模型,Inteface和Service是接口和实现分开的,Util是全项目通用,或者是跨项目通用的,跟业务逻辑没有任何关系的。

写接口最大的好处就是在你写的Controller代码,或者是Service里的主要业务逻辑代码的时候,屏蔽掉细节。

写一个业务逻辑的时候,比如说修真院的加入班级。

第一步,做校验,用户是否为空,班级是否不存在,是否已经加入了班级等等。

第二步,更新班级和用户的关系表,更新班级总人数,更新职业总人数,更新用户的最新班级ID。

第三步,发送系统通知,告知用户加入班级成功。

如果说不用接口,只用实现类的话,第一种方式就是把所有的代码都写在这个Controller里去,代码会非常非常繁琐,一个函数突破几千行轻轻松松,而且改动起来很麻烦。

第二种方式就是抽象出来函数。这种方式在某种程度上能够解决代码块大的问题,但是你必须要New一个实现类出来,想想在上述逻辑中,需要new几个实现类?这些实现类就会被New的各处都是,甚至改个名字都很蛋疼。

但是如果你使用接口的话,你会发现,接口是强制于你去将复杂的业务逻辑抽象成具体做的事儿。

比如说,

if(user==null){

// to do something

}

就变成了CheckUser(uid)这么一个接口。实现类也明确了自已要做的事情。

从某种程度上来说,抽象成一个私有方法也能解决这个问题,但是一般都会推荐,如果你发现你写了很多私有方法,要么是他们可以继续演化成一个util,要么是可以成为一个Service。

dao是什么?

定义优势

DAO (DataAccessobjects 数据存取对象)是指位于业务逻辑和持久化数据之间实现对持久化数据的访问。通俗来讲,就是将数据库操作都封装起来。在面向对象设计过程中,有一些”套路”用于解决特定问题称为模式。DAO 模式提供了访问关系型数据库系统所需操作的接口,将数据访问和业务逻辑分离对上层提供面向对象的数据访问接口。从以上 DAO 模式使用可以看出,DAO 模式的优势就在于它实现了两次隔离:

  1. 隔离了数据访问代码和业务逻辑代码。业务逻辑代码直接调用DAO方法即可,完全感觉不到数据库表的存在。分工明确,数据访问层代码变化不影响业务逻辑代码,这符合单一职能原则,降低了藕合性,提高了可复用性。
  2. 隔离了不同数据库实现。采用面向接口编程,如果底层数据库变化,如由 MySQL 变成 Oracle 只要增加 DAO 接口的新实现类即可,原有 MySQ 实现不用修改。这符合 “开-闭” 原则。该原则降低了代码的藕合性,提高了代码扩展性和系统的可移植性。

典型的DAO 模式的主要组成:

1、DAO接口: 把对数据库的所有操作定义成抽象方法,可以提供多种实现。
2、DAO 实现类: 针对不同数据库给出DAO接口定义方法的具体实现。
3、实体类:用于存放与传输对象数据。
4、数据库连接和关闭工具类: 避免了数据库连接和关闭代码的重复使用,方便修改。

示例

  • 先看看DAO接口:
1
2
3
4
5
6
public interface PetDao {
/**
* 查询所有宠物
*/
List<Pet> findAllPets() throws Exception;
}
  • 再看看DAO实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class PetDaoImpl extends BaseDao implements PetDao {
/**
* 查询所有宠物
*/
public List<Pet> findAllPets() throws Exception {
Connection conn=BaseDao.getConnection();
String sql="select * from pet";
PreparedStatement stmt= conn.prepareStatement(sql);
ResultSet rs= stmt.executeQuery();
List<Pet> petList=new ArrayList<Pet>();
while(rs.next()) {
Pet pet=new Pet(
rs.getInt("id"),
rs.getInt("owner_id"),
rs.getInt("store_id"),
rs.getString("name"),
rs.getString("type_name"),
rs.getInt("health"),
rs.getInt("love"),
rs.getDate("birthday")
);
petList.add(pet);
}
BaseDao.closeAll(conn, stmt, rs);
return petList;
}
}
  • 宠物实体类(里面get/set方法就不列出了):
1
2
3
4
5
6
7
8
9
10
public class Pet {
private Integer id;
private Integer ownerId; //主人ID
private Integer storeId; //商店ID
private String name; //姓名
private String typeName; //类型
private int health; //健康值
private int love; //爱心值
private Date birthday; //生日
}
  • 连接数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class BaseDao {
private static String driver="com.mysql.jdbc.Driver";
private static String url="jdbc:mysql://127.0.0.1:3306/epet";
private static String user="root";
private static String password="root";
static {
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, user, password);
}

public static void closeAll(Connection conn,Statement stmt,ResultSet rs) throws SQLException {
if(rs!=null) {
rs.close();
}
if(stmt!=null) {
stmt.close();
}
if(conn!=null) {
conn.close();
}
}


public int executeSQL(String preparedSql, Object[] param) throws ClassNotFoundException {
Connection conn = null;
PreparedStatement pstmt = null;
/* 处理SQL,执行SQL */
try {
conn = getConnection(); // 得到数据库连接
pstmt = conn.prepareStatement(preparedSql); // 得到PreparedStatement对象
if (param != null) {
for (int i = 0; i < param.length; i++) {
pstmt.setObject(i + 1, param[i]); // 为预编译sql设置参数
}
}
ResultSet num = pstmt.executeQuery(); // 执行SQL语句
} catch (SQLException e) {
e.printStackTrace(); // 处理SQLException异常
} finally {
try {
BaseDao.closeAll(conn, pstmt, null);
} catch (SQLException e) {
e.printStackTrace();
}
}
return 0;
}

}

转自此处

事务

MySQL 事务

MySQL 事务主要用于处理操作量大,复杂度高的数据。比如说,在人员管理系统中,你删除一个人员,你既需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成一个事务!

  • 在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务。
  • 事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行。
  • 事务用来管理 insert,update,delete 语句

一般来说,事务是必须满足4个条件(ACID)::原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。

  • 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
  • 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
  • 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
  • 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

在 MySQL 命令行的默认设置下,事务都是自动提交的,即执行 SQL 语句后就会马上执行 COMMIT 操作。因此要显式地开启一个事务务须使用命令 BEGIN 或 START TRANSACTION,或者执行命令 SET AUTOCOMMIT=0,用来禁止使用当前会话的自动提交。

事务控制语句:

  • BEGIN 或 START TRANSACTION 显式地开启一个事务;
  • COMMIT 也可以使用 COMMIT WORK,不过二者是等价的。COMMIT 会提交事务,并使已对数据库进行的所有修改成为永久性的;
  • ROLLBACK 也可以使用 ROLLBACK WORK,不过二者是等价的。回滚会结束用户的事务,并撤销正在进行的所有未提交的修改;
  • SAVEPOINT identifier,SAVEPOINT 允许在事务中创建一个保存点,一个事务中可以有多个 SAVEPOINT;
  • RELEASE SAVEPOINT identifier 删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;
  • ROLLBACK TO identifier 把事务回滚到标记点;
  • SET TRANSACTION 用来设置事务的隔离级别。InnoDB 存储引擎提供事务的隔离级别有READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ 和 SERIALIZABLE。

MYSQL 事务处理主要有两种方法:

1、用 BEGIN, ROLLBACK, COMMIT来实现

  • BEGIN 开始一个事务
  • ROLLBACK 事务回滚
  • COMMIT 事务确认

2、直接用 SET 来改变 MySQL 的自动提交模式:

  • SET AUTOCOMMIT=0 禁止自动提交
  • SET AUTOCOMMIT=1 开启自动提交

事务测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
mysql> use RUNOOB;
Database changed
mysql> CREATE TABLE runoob_transaction_test( id int(5)) engine=innodb; # 创建数据表
Query OK, 0 rows affected (0.04 sec)

mysql> select * from runoob_transaction_test;
Empty set (0.01 sec)

mysql> begin; # 开始事务
Query OK, 0 rows affected (0.00 sec)

mysql> insert into runoob_transaction_test value(5);
Query OK, 1 rows affected (0.01 sec)

mysql> insert into runoob_transaction_test value(6);
Query OK, 1 rows affected (0.00 sec)

mysql> commit; # 提交事务
Query OK, 0 rows affected (0.01 sec)

mysql> select * from runoob_transaction_test;
+------+
| id |
+------+
| 5 |
| 6 |
+------+
2 rows in set (0.01 sec)

mysql> begin; # 开始事务
Query OK, 0 rows affected (0.00 sec)

mysql> insert into runoob_transaction_test values(7);
Query OK, 1 rows affected (0.00 sec)

mysql> rollback; # 回滚
Query OK, 0 rows affected (0.00 sec)

mysql> select * from runoob_transaction_test; # 因为回滚所以数据没有插入
+------+
| id |
+------+
| 5 |
| 6 |
+------+
2 rows in set (0.01 sec)

mysql>

PHP中使用事务实例

MySQL ORDER BY 测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
$dbhost = 'localhost'; // mysql服务器主机地址
$dbuser = 'root'; // mysql用户名
$dbpass = '123456'; // mysql用户名密码
$conn = mysqli_connect($dbhost, $dbuser, $dbpass);
if(! $conn )
{
die('连接失败: ' . mysqli_error($conn));
}
// 设置编码,防止中文乱码
mysqli_query($conn, "set names utf8");
mysqli_select_db( $conn, 'RUNOOB' );
mysqli_query($conn, "SET AUTOCOMMIT=0"); // 设置为不自动提交,因为MYSQL默认立即执行
mysqli_begin_transaction($conn); // 开始事务定义

if(!mysqli_query($conn, "insert into runoob_transaction_test (id) values(8)"))
{
mysqli_query($conn, "ROLLBACK"); // 判断当执行失败时回滚
}

if(!mysqli_query($conn, "insert into runoob_transaction_test (id) values(9)"))
{
mysqli_query($conn, "ROLLBACK"); // 判断执行失败时回滚
}
mysqli_commit($conn); //执行事务
mysqli_close($conn);
?>
  • 本文标题:武功滞涩处Java篇
  • 本文作者:Nico Liao
  • 创建时间:2022-03-21 16:05:21
  • 本文链接:https://www.lzp.zone/2022/03/21/武功滞涩处(java篇)/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论