一.ATM实战开发的简介。

 学习了几天的Oracle,开始着手用数据库PL/SQL语言做一个简单的ATM取款机业务,主要是为了巩固数据库的知识,并非真正的去实现高端的业务。有兴趣的可以看看,希望对同胞们都有用。

  •  ATM的表。它有四张表,用户信息表userinfo,卡号信息表cardinfo,交易信息表tradeinfo,存款类型表deposit。
  用户信息表userInfo
customerID int 客户编号 主键
customerName varchar(10) 用户名 not null
personID varcahr(20) 身份证号

not null unique 只能是15位

或者18位符合实际的身份证号

telephone varcahr(20) 联系电话

not null,格式为xxxx-xxxxxxxx或者xxx-xxxxxxxx

或者11手机号

address varchar(30) 居住地址 可选
1
2
3
4
5
6
7
8
9
10
11
12
13
--创建userinfo表--
create table UserInfo(
  customerID int primary key,
  customerName varchar(10) not null,
  personID varchar(20) not null unique,
  telephone varchar(20) not null
  address varchar(30)
);
--为身份证号和电话增加正则表达式约束--
alter table userinfo add constraint CK_TELEPHONE 
check(regexp_like(telephone,'^1[3,4,5,7,8][0-9]-[0-9]{8}$|^[0-9]{3,4}-[0-9]{8}$'));
alter table userinfo add constraint CK_PERSONID
check(regexp_like(personid,'^[0-9]{15}$|^[0-9]{17}[0-9,x]$'));
卡号信息表cardInfo
cardID varchar(30) 卡号

主键,如1010 3576 xxxx xxxx,

每4位后面有空格,卡号随机产生。

curID varchar(5) 货币种类 必填,默认为RMB
savingID varchar(5) 存款类型 外键,必填。
openDate date 开户日期 必填。默认为当前时间
openMoney decimal(10,2) 开户金额 必填,不低于1.
balance decimal(10,2) 余额 必填,不低于1.
pwd varchar(10) 密码 必填,6位数字。默认为888888
isReportLoss char(2) 是否挂失 必填,只能是'是'或'否'。默认为'否'

  customerid    int    开户编号        外键,必填。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
--创建cardinfo表--
create table CardInfo(
  cardID varchar(30)  primary key,
  curID varchar(5) default 'RMB' not null,
  savingID varchar(5) not null,
  openDate date default sysdate not null,
  openMoney decimal(10,2) not null check(openMoney>=1),
  balance decimal(10,2) not null check(balance>=1),
  pwd varchar(10) default '888888' not null ,
  IsReportLoss char(2) default '否' not null,
  customerID int not null references UserInfo(customerID) 
);
--为卡号和密码增加正则表达式约束--
alter table cardinfo add constraint CK_PWD check
(regexp_like(pwd,'^[0-9]{6}$'));
  
alter table cardinfo add constraint CK_CARDID check
(regexp_like(cardid,'^1010[[:space:]]3576[[:space:]][0-9]{4}[[:space:]][0-9]{4}$'));
交易信息表tradeInfo
transdate date 交易日期 必填。默认为系统时间
cardID varchar(30) 卡号 外键,必填。
transType varchar(10) 交易类型

必填,只能是存入或者支取

transmoney decimal(10,2) 交易金额 必填,大于0
remark varchar(50) 备注 可选
1
2
3
4
5
6
7
8
--创建tradeinfo表--
create table TradeInfo(
  transDate date default sysdate not null,
  cardID varchar(30) not null references CardInfo(cardID), 
  transType varchar(10) not null,
  transMoney decimal(10,2) not null,
  remark varchar(50)
);

  --为transtype增加约束--
  alter table tradeinfo add constraint CK_TRANSTYPE check (transtype in('支取','存入'));

存款类型表deposit
savingID int 类型编号 主键 
savingName varchar(20) 存款类型名称 not null unique
  • ATM模拟实现的业务。

   1.修改密码。

   2.挂失。

   3.查询本周开户的卡号。

   4.查询本月一次性交易金额最高的卡号。

   5.查询卡号挂失的用户的信息。

   6.开户。

   7.存款或者取款。

二.插入数据。

  •    为deposit表插入数据。

   插入数据前我们应该有这样一个认识,与客户无关的信息表先插入数据,与客户有关的次之。因为你开户的时候,客户存款类型必须与存款类型表deposit表的一条记录匹配才行。这就像一个银行一样,不管有多少客户,你银行本身的数据以及功能是必须有的。所以,先插入与银行的信息有关与客户无关的表的数据。

   为deposit插入数据的时候,存款类型编号我们一般也不会自己去输入,所以要创建一个插入类型名称时,自动插入类型编号的触发器。

   sequence savingid_incr --创建序列不能加or replace

 
start with 1
increment by 1
nomaxvalue
nocycle
cache 30;
 
 
 
 

    然后创建savingid的触发器。

1
2
3
4
5
6
7
8
--创建savingid的触发器--
create or replace trigger savingid_insert
before insert on deposit
for each row
declare
begin
  :new.savingid:=savingid_incr.nextval;
end;

    现在就可以插入数据了。

1
2
3
insert into deposit(savingname) values('定期');
insert into deposit(savingname) values('活期期');
insert into deposit(savingname) values('定活两便');

    检测数据。select * from deposit;可以看到三条savingid自动产生并插入的记录。

  •    为userinfo表和cardinfo表插入数据:

     对于userinfo表,在插入数据的时候,我们不可能每次都去插入客户编号,所以要为客户编号创建一个插入其他数据时的客户编号自动插入的触发器;还要为卡号创建一个随机产生的过程,并且开户的时候通过过程去插入,因为卡号是随机产生并且不能重复,所以我把这个判断写入了开户的业务逻辑中。

     在这里的话,我直接去实现开户,然后通过开户为userinfo表和cardinfo表插入数据,东西很多,我们一步一步来。

     先为customerid创建序列。

1
2
3
4
5
6
create  sequence id_incr --创建序列不能加or replace
start with 1001
increment by 1
nomaxvalue
nocycle
cache 30;

     再为customerid创键触发器。

1
2
3
4
5
6
7
8
create or replace trigger id_insert
before insert on userinfo
for each row
declare
next_customerid  userinfo.customerid%type;
begin
  :new.customerid:=id_incr.nextval;
end;

    为cardid创建随机产生的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
create or replace procedure r_cardid(out_id out varchar2)
as
   r_num number;
   front_id varchar2(4);
   back_id varchar2(4);
   real_id varchar2(20);
begin
  select lpad(trunc(dbms_random.value*100000000),8,0)into r_num from dual;
  front_id:=to_char(substr(r_num,1,4));
  back_id:=to_char(substr(r_num,5,4));
  real_id:='1010 3576 '||front_id||' '||back_id;
  out_id:=real_id;
end;<BR>

   开户的时候,除了customerid和cardid以及表给的默认值,其他都是与用户开户有关的信息。所以准备工作做好之后,我们就可以实现开户的业务了。

*****************************************************开户******************************************************

******开户的数据*********

  开户时需要用户输入的有:

    身份证号——personid
    存款类型——savingid
    存款金额——openmoney
    本卡密码——pwd
    (在开户时如果用户是第一次开户的话,就又需要的输入的有:)
    姓名——customername
    联系方式——telephone
    地址——address

  系统自动赋予的值有:
    用户号——customerid(触发器触发)
    卡号——cardid(调用r_cardid(outid)过程)

    其他都为系统给的默认值。

******开户的存储过程逻辑******

    1.先判断用户是不是第一个开卡,如果是,那么先创建用户信息,再开户。

    2.用户创建之后或者用户已存在时,调用卡号随机产生的存储过程,产生一个随机产生的不重复的卡号。

    3.卡号产生之后,判断用户输入的存款类型是否正确,正确,就可以开户了。不正确则撤销之前所有的操作并且提示开户终止。

    4.开户就是插入一条符合逻辑的cardinfo记录,并且提示开户的最终信息。

******开户的存储过程************

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
create or replace procedure openAccount(
temp_personid in userinfo.personid%type,
temp_savingname in deposit.savingname%type,
temp_openmoney in cardinfo.openmoney%type,
temp_pwd in cardinfo.pwd%type,
temp_customername in userinfo.customername%type,
temp_telephone in userinfo.telephone%type,
temp_address in userinfo.address%type)
as
  isnullpersonid userinfo.personid%type; --select into判断身份证号是否存在。
  temp_cardid cardinfo.cardid%type; --select into判断新产生的卡号是否存在,并且在后来是要用的。
  temp_savingid cardinfo.savingid%type;--select into 判断用户输入的存款类型是否可用。
  temp_customerid userinfo.customerid%type;
begin
  begin
  --判断用户是否存在
  select personid into isnullpersonid from userinfo where personid=temp_personid;
  exception
  when no_data_found then
    -----创建用户----
    insert into userinfo(customername,personid,telephone,address) 
    values(temp_customername,temp_personid,temp_telephone,temp_address);
  end;
  begin
    while 1=1 loop --产生一个唯一不重复的卡号
      r_cardid(temp_cardid);
    select cardid into temp_cardid from cardinfo where cardid=temp_cardid;
    --如果没有找到,则证明新产生的卡号是唯一的,进入exception继续完成操作。
    end loop;
  exception
    when no_data_found then
    --来到这里说明产生的卡号是可用的,接下来就应该判断存款类型temp_savingid.
    begin
      select  savingid into temp_savingid from deposit where savingname=temp_savingname; 
      --如果存在,那么就可以开户了,如果不存在,则撤销之前的所有操作,用事务。
        
      --customerid是之前就有或者在开户中自动产生的,所以这里要通过SQL找到它。
      select customerid into temp_customerid from userinfo where personid=temp_personid;
      --开户---
      insert into cardinfo(cardid,savingid,openmoney,balance,pwd,customerid) 
      values(temp_cardid,temp_savingid,temp_openmoney,temp_openmoney,temp_pwd,temp_customerid);
      dbms_output.put_line('      开户成功!');
      dbms_output.put_line('您的银行卡号为:'||temp_cardid);
      dbms_output.put_line('开户日期:'||sysdate||'   开户金额 '||temp_openmoney);
    exception
      when no_data_found then
      rollback;--撤销之前的所有操作
      raise_application_error(-20000, '存款类型不正确,开户终止!');
    end;
  end;
end;

***************利用开户存储过程来插入数据******************

插入一条用户第一次开户的数据:

set serveroutput on;
execute openAccount(410181199308084016,'定期',50000,762723,'徐万轩','151-03891462','河南省郑州市');

插入一条老用户开户的数据:

set serveroutput on;
execute openAccount(410181199308084016,'活期',50000,762723,'徐万轩','151-03891462','河南省郑州市');

此时查表就会发现有两条cardinfo记录,一条userinfo记录。

  •    为tradeinfo表插入数据。

    tradeinfo表示记录交易信息的,所以我们可以调用存取款的过程来为tradeinfo表插入数据。

*****************************************************存取款存储过程*******************************************

******存取款需要用户的数据**********

  需要用户输入的信息:

    存款或者取款的金额:temp_money
    存款或者取款的类型:temp_transtype
    银行卡号:temp_cardid

******存取款的实现逻辑*************

  1.首先判断用户输入的卡号是否存在,不存在则退出操作。

  2.卡号存在,再判断卡是否挂失,如果挂失,提醒并退出操作。

  3.没有挂失的话,判断存取款类型是否正确,如果正确,就可以存取款了。

  4.存取款时,更新cardinfo表卡的balance余额,并且插入一条交易信息表。

  5.如果取款之后,余额小于1,就会发生检查约束错误,捕获错误并且利用事务的原理rollback之前的操作。

*******存取款的存储过程**************

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
create or replace procedure inout_money(
temp_money in number,
temp_transtype in tradeinfo.transtype%type,
temp_cardid in cardinfo.cardid%type
)
as
  isnulltranstype  tradeinfo.transtype%type;--判断存取款类型是否正确
  isnullcardid  cardinfo.cardid%type;--判断银行卡是否存在
  isnullloss  cardinfo.isreportloss%type;--判断银行卡是否冻结
begin
  begin  
    --判断卡号是否存在
    select cardid into isnullcardid from cardinfo where cardid=temp_cardid;
  exception
    when no_data_found then
      begin
        raise_application_error(-20000,'卡号不存在!');
      end;
  end;
  begin
    --先判断卡号是否冻结
    select isreportloss into isnullloss from cardinfo where cardid=temp_cardid;
    if isnullloss='是' then
      raise_application_error(-20001,'该卡已冻结,不能执行该操作!');
    end if;
    --判断存取款类型是否存在
    select distinct transtype into isnulltranstype from tradeinfo where transtype=temp_transtype; 
    if temp_transtype='支取'then
      update cardinfo set balance=balance-temp_money where cardid=temp_cardid;
      insert into tradeinfo(cardid,transtype,transmoney)
      values(temp_cardid,temp_transtype,temp_money);
      dbms_output.put_line('取出'||temp_money||'RMB!');
    elsif temp_transtype='存入' then
      update cardinfo set balance=balance+temp_money where cardid=temp_cardid;
      insert into tradeinfo(cardid,transtype,transmoney)
      values(temp_cardid,temp_transtype,temp_money);
      dbms_output.put_line('存入'||temp_money||'RMB!');
    end if;
  exception
    when no_data_found then
      rollback;
      raise_application_error(-20002,'存取款类型不正确!'); 
    when others then
      dbms_output.put_line('余额不能少于1');
      rollback;
        
  end;
end;

*************利用存取款存储过程来插入tradeinfo数据*****************

  set serveroutput on;
  execute inout_money(500,'存入','1010 3576 1685 3672');

  set serveroutput on;
  execute inout_money(500,'支取','1010 3576 1685 3672');

  这时,trande表会有两条记录,一个是1010 3576 1685 3672支取的记录,一个是1010 3576 1685 3672的存入记录。

  其实,开户和存取款的过程与插入数据时两回事,但是我们却可以利用这两个过程来实现数据的插入,本人在这里也是懒省事。其实,这样插入数据的话,思路上也比较好理解。毕竟,对于练习来说,我们在实现业务之前的数据只是为了检查这些数据的插入是否满足表的约束和它的合理性,但是,在实际的开发过程中,一个业务的完成的前提条件是你必须有实现这个业务的功能,所以先实现业务,通过业务来插入数据时最为合理的。不过这样对于初学者或者基础不太扎实的同胞可能就有些难以理解了,没事,慢慢来吧。

三.实现业务逻辑。

  • 挂失。

 ********挂失需要用户输入的信息**********

  1.卡号。

  2.密码。

  3.账户ID。

  ******挂失的存储过程*********

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
create or replace procedure lose(temp_cardid in cardinfo.cardid%type,
temp_pwd in number,temp_customerid in varchar2)
as
  row_info cardinfo%rowtype;
  islose varchar2(5);
begin 
  select isreportloss into islose from cardinfo where cardid=temp_cardid;
  if islose='是' then
    dbms_output.put_line('此卡已经挂失!');
    goto last_point;
  end if;
  select * into row_info from cardinfo  
  where cardid=temp_cardid;
  if row_info.pwd=temp_pwd and row_info.customerid=temp_customerid then
    update cardinfo set IsReportLoss='是' where cardid=temp_cardid;
    dbms_output.put_line('挂失成功!');
  else 
    dbms_output.put_line('对不起,您输入卡的信息不正确,不能挂失!');
  end if;
  <<last_point>>
  null;
exception
  when NO_DATA_FOUND then
    dbms_output.put_line('您输入的卡号不存在!');
end;

  **********测试***********

  set serveroutput on;
  execute lose('1010 3576 4654 1134','888866','1001');

  • 修改密码。

  ********修改密码所需的用户的信息********

  1.卡号

  2.密码

  **********修改密码的存储过程**********

1
2
3
4
5
6
7
8
9
10
11
12
13
14
create or replace procedure up_pwd(temp_cardid in varchar2,
temp_newpwd in number)
as
temp varchar2(30);
BEGIN
  select cardid into temp from cardinfo where cardid=temp_cardid;
  update cardinfo set pwd=temp_newpwd where cardid=temp_cardid;
  dbms_output.put_line('修改密码成功!');
EXCEPTION
  when no_data_found then
    dbms_output.put_line('输入的卡号不存在!');
  when others then
    dbms_output.put_line('违反检查约束');
END;

  *********修改密码的测试*************

    set serveroutput on;
    execute up_pwd('1010 3576 0030 0000','666652');

  • 查询本周开户的卡号。

1
select * from cardinfo where opendate>=trunc(sysdate,'day');
  • 查询本月一次性交易金额最高的卡号。

1
2
3
4
select * from tradeinfo;
--from后面跟本月的搜索结果,where处控制transmoney为最大值。
select distinct * from(select * from tradeinfo where transdate>trunc(sysdate,'month')) 
where transmoney in( select max(transmoney) from tradeinfo);
  • 查询卡号挂失的用户的信息。

1
2
3
4
select u.customername,u.customerid,u.personid,u.telephone,u.address
from userinfo u inner join cardinfo c on 
c.customerid=u.customerid 
where c.isreportloss='是';

  

四.开发中遇到的一些问题。

  在开发的过程中,遇到了许多问题,因为我学习Oracle数据库也就十天左右的时间,对于一些基本的还不是很熟悉。

  遇到的问题:

    1.创建过程的时候不能有declare关键字。

    2.goto流程控制不能goto到exception执行体中。

    3.select into语句千万别返回多行语句,在编译的时候是不会出错的,调用的时候出错也会提示较多的错误,修改很麻烦。比如,查看存款类型是否合法的时候,就需要在select语句前加上distinct来确保返回的是一条语句。

    4.创建序列不可以使用 or replace。

    5.添加行级触发器的时候,赋值用 :new.属性值来赋值。如果select into语句中也赋值的话,就会用两次序列自动产生的值,所以编译器不会报错,但是你的序列的增长率却是你想要的二倍。

    6.在像开户这样的逻辑实现过程中,需要多次去用select去判断。而且它还有判断之后需要共同实现的部分,所以对于我一些新手来说,还是比较难于理解begin end的嵌套结构。bengin  end里面可以嵌套begin end;exception之后的when then也可以接着begin end并且也可以嵌套。

    7.日期函数不是很熟悉。

      tranc(date,day)日期date所在周的周日的日期。

      interval '2' month将2作为month来运算。日期的加减默认是天。

      last_day(date),date日期所在月的最后一天的日期。

      add_months(date,2) date日期两个月之后的日期。

    8.数值函数不是很熟悉。

      dbms_random.value产生0-1之间的小数,精确到很多位。

      lpad(对象,位数,0)向对象左侧填充0,知道对象的位数=输出的位数。

      trunc(数值对象[,截取精度]),如果截取精度>0,则截取到小数点后第几位,如果截取精度<0,则截取到小数点前第几位,截取的部分用0填充。如果截取精度=0,则去掉小数部分。截取精度不写的话默认为0.

ATM取款机的数据库模拟开发和实战总结的更多相关文章

  1. Oracle实战训练——ATM取款机业务

    ATM取款机的数据库模拟开发和实战总结 一.ATM实战开发的简介. 学习了几天的Oracle,开始着手用数据库PL/SQL语言做一个简单的ATM取款机业务,主要是为了巩固数据库的知识,并非真正的去实现 ...

  2. ATM取款机模拟——数据结构课设

    今天帮人写的第二篇课设 . ;-) 机智的窝 要求:大概说一下吧,就是要创建一个用户(初始化一账户),模拟ATM的业务(取款,100的整数倍,改密               码,查剩余金额.等等,各 ...

  3. 自己动手模拟开发一个简单的Web服务器

    开篇:每当我们将开发好的ASP.NET网站部署到IIS服务器中,在浏览器正常浏览页面时,可曾想过Web服务器是怎么工作的,其原理是什么?“纸上得来终觉浅,绝知此事要躬行”,于是我们自己模拟一个简单的W ...

  4. 大话JS面向对象之开篇万物皆对象------(ATM取款机引发的深思)

    一,总体概要 OO(面向对象)概念的提出是软件开发工程发展的一次革命,多年来我们借助它使得很多大型应用程序得以顺利实现.如果您还没有掌握并使用OO进行程序设计和开发,那么您无疑还停留在软件开发的石器时 ...

  5. 用grunt搭建自动化的web前端开发环境实战教程(详细步骤)

    用grunt搭建自动化的web前端开发环境实战教程(详细步骤) jQuery在使用grunt,bootstrap在使用grunt,百度UEditor在使用grunt,你没有理由不学.不用!前端自动化, ...

  6. 【Android开发VR实战】二.播放360&#176;全景视频

    转载请注明出处:http://blog.csdn.net/linglongxin24/article/details/53924006 本文出自[DylanAndroid的博客] [Android开发 ...

  7. 【Android开发VR实战】三.开发一个寻宝类VR游戏TreasureHunt

    转载请注明出处:http://blog.csdn.net/linglongxin24/article/details/53939303 本文出自[DylanAndroid的博客] [Android开发 ...

  8. DirectX12 3D 游戏开发与实战第八章内容(下)

    DirectX12 3D 游戏开发与实战第八章内容(下) 8.9.材质的实现 下面是材质结构体的部分代码: // 简单的结构体来表示我们所演示的材料 struct Material { // 材质唯一 ...

  9. 前端开发css实战:使用css制作网页中的多级菜单

    前端开发css实战:使用css制作网页中的多级菜单 在日常工作中,大家都会遇到一些显示隐藏类菜单,比如页头导航.二维码显示隐藏.文本提示等等......而这些效果都是可以使用纯css实现的(而且非常简 ...

随机推荐

  1. supervisor备忘

    supervisor是把普通app变成deamon的工具,虽然没有erlang的supervise粒度那么细,但是已经非常方便了 安装 sudo apt-get install supervisor ...

  2. 初识C++模板元编程(Template Mega Programming)

    前言:毕设时在开源库上做的程序,但是源码看得很晕(当时导师告诉我这是模板元编程,可以不用太在乎),最近自己造轮子时想学习STL的源码,但也是一样的感觉,大致了解他这么做要干什么,但是不知道里面的机制. ...

  3. 利用反射及ActionFilterAttribute实现MVC权限管理

    1.利用反射获取当前程序集下的所有控制器和方法,拼接后写入到数据库. public void GetRightInfo() { ; var controllerTypes = Assembly.Get ...

  4. 用活firewalld防火墙中的zone

    原文地址:http://www.excelib.com/article/290/show firewalld中zone的含义学生前面已经给大家介绍过了,说白了一个zone就是一套规则集.可是什么时候该 ...

  5. android视频处理相关资料

    <开源>基于Android的点对点视频通信/RTSP/RTP/H.264 http://blog.csdn.net/cazicaquw/article/details/8650543 历经 ...

  6. 20165226 实验三 敏捷开发与XP实践

    实验三 敏捷开发与XP实践 实验目的 一.安装alibaba 插件,解决代码中的规范问题.在IDEA中使用工具(Code->Reformate Code)格式化代码,研究一下Code菜单,比较功 ...

  7. laravel 做图片的缩略图 踩坑

    系统需求 PHP >= 5.3 Fileinfo Extension GD Library (>=2.0) … or … Imagick PHP extension (>=6.5.7 ...

  8. npm 自身的升级

    npm 是随 NodeJS 一起发布的包管理工具,默认采用的并不一定是最新版本,某些情况下(比如使用了某个IDE)需要最新版本的 npm , 咋办?单独升级,官方的几种说法的操作比较复杂,本经验就是说 ...

  9. 第七章 HTTP流量管理(一) gateway

    Gateway: istio的一种资源类型,Istio Gateway告诉k8s的istio-ingressgateway pods可以打开哪些主机和端口(如下的80是 ingressgateway ...

  10. python学习(二十四) 字符串格式化

    1: Test 1 a = 'city' b = 'country' print(" aaa %s bbb %s " % (a, b)) result: aaa city bbb ...