`

Java 通用数据库连接类[支持存储过程 参数自动识别]

    博客分类:
  • j2se
阅读更多
package com.hospital.dao.tools;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import org.apache.log4j.Logger;

/**
 * 数据库操作管理类
 * 
 * @author Harlyhood
 * 
 */
public class DBManager {

	// --------------------------------------------------------- Instance
	private static Logger logger = Logger.getLogger(DBManager.class);
	// --------------------------------------------------------- Methods

	// 数据库连接对象
	private Connection con;
	// SQL语句对象
	private Statement stmt;
	// 带参数的Sql语句对象
	private PreparedStatement pstmt;
	// 记录集对象
	private ResultSet rs;
	// 数据连接管理(连接池对象)
	private DBConnectionManager dcm = null;

	/** ***********************手动设置的连接参数********************************* */
	@SuppressWarnings("unused")
	private static String _DRIVER = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
	@SuppressWarnings("unused")
	private static String _URL = "jdbc:sqlserver://localhost:1433;database=Hospital_AI_DB;characterEncoding=gb2312";
	@SuppressWarnings("unused")
	private static String _USER_NA = "sa";
	@SuppressWarnings("unused")
	private static String _PASSWORD = "";

	/** ********************************************************************** */

	// 默认构造
	public DBManager() {
	}

	/** ****************************************************************************************** */
	/**
	 * **************************************** 数据库连接初始化
	 * ***********************************
	 */
	/** ****************************************************************************************** */

	/**
	 * 得到一个默认的数据库连接[从 com.hospital.dao.tools.db.properties文件初始化]
	 * 
	 * @throws Exception
	 */
	private void getConnection() {
		logger.info("###############open:::::从默认的配置文件得到一个数据库连接");
		// 获取一个连接池管理类的实例
		dcm = DBConnectionManager.getInstance();
		// 得到一个数据库连接
		con = dcm.getConnection("mysql");

		try {
			con.setAutoCommit(false);
		} catch (SQLException e) {

			e.printStackTrace();
		}
	}

	/**
	 * 从指定参数得到一个连接对象
	 * 
	 * @param driver
	 * @param url
	 * @param user_na
	 * @param password
	 * @throws Exception
	 */
	public void getConnection(String driver, String url, String user_na,
			String password) throws Exception {
		try {
			logger.info("###############open:::::从指定配置中得到一个数据库连接");
			Class.forName(driver);
			con = DriverManager.getConnection(url, user_na, password);
		} catch (ClassNotFoundException ex) {
			logger
					.info("###############Error[com.hospital.dao.tools.DBManager^^^Method:getConnection^^^Line:81]找不到类驱动类: "
							+ driver);
			throw ex;
		} catch (SQLException ex) {
			logger
					.info("###############Error[com.hospital.dao.tools.DBManager^^^Method:getConnection^^^Line:81]加载类: "
							+ driver + " 时出现 SQLException 异常");
			throw ex;
		}
	}

	/** ****************************************************************************************** */
	/**
	 * **************************************** 数据库操作方法
	 * ***********************************
	 */
	/** ****************************************************************************************** */

	/**
	 * 执行SQL语句操作(更新数据 无参数)
	 * 
	 * @param strSql
	 *            SQL语句
	 * @throws Exception
	 */
	public boolean executeUpdate(String strSql) throws SQLException {
		getConnection();
		// getConnection(_DRIVER,_URL,_USER_NA,_PASSWORD);
		boolean flag = false;
		stmt = con.createStatement();
		logger.info("###############::执行SQL语句操作(更新数据 无参数):" + strSql);
		try {
			if (0 < stmt.executeUpdate(strSql)) {
				close_DB_Object();
				flag = true;
				con.commit();
			}
		} catch (SQLException ex) {
			logger
					.info("###############Error DBManager Line126::执行SQL语句操作(更新数据 无参数):"
							+ strSql + "失败!");
			flag = false;
			con.rollback();
			throw ex;
		}
		return flag;

	}

	/**
	 * 执行SQL语句操作(更新数据 有参数)
	 * 
	 * @param strSql
	 *            sql指令
	 * @param prams
	 *            参数列表
	 * @return
	 * @throws SQLException
	 */
	public boolean executeUpdate(String strSql, HashMap<Integer, Object> prams)
			throws SQLException, ClassNotFoundException {
		getConnection();
		// getConnection(_DRIVER,_URL,_USER_NA,_PASSWORD);
		boolean flag = false;
		try {
			pstmt = con.prepareStatement(strSql);
			setParamet(pstmt, prams);
			logger.info("###############::执行SQL语句操作(更新数据 有参数):" + strSql);

			if (0 < pstmt.executeUpdate()) {
				close_DB_Object();
				flag = true;
				con.commit();
			}
		} catch (SQLException ex) {
			logger
					.info("###############Error DBManager Line121::执行SQL语句操作(更新数据 无参数):"
							+ strSql + "失败!");
			flag = false;
			con.rollback();
			throw ex;
		} catch (ClassNotFoundException ex) {
			logger
					.info("###############Error DBManager Line152::执行SQL语句操作(更新数据 无参数):"
							+ strSql + "失败! 参数设置类型错误!");
			con.rollback();
			throw ex;
		}
		return flag;

	}

	/**
	 * 执行SQL语句操作(查询数据 无参数)
	 * 
	 * @param strSql
	 *            SQL语句
	 * @return 数组对象列表
	 * @throws Exception
	 */
	public ArrayList<HashMap<Object, Object>> executeSql(String strSql)
			throws Exception {
		getConnection();
		// getConnection(_DRIVER,_URL,_USER_NA,_PASSWORD);
		stmt = con.createStatement();
		logger.info("###############::执行SQL语句操作(查询数据):" + strSql);
		rs = stmt.executeQuery(strSql);
		con.commit();
		if (null != rs) {
			return convertResultSetToArrayList(rs);
		}
		close_DB_Object();
		return null;
	}

	/**
	 * 执行SQL语句操作(查询数据 有参数)
	 * 
	 * @param strSql
	 *            SQL语句
	 * @param prams
	 *            参数列表
	 * @return 数组对象列表
	 * @throws Exception
	 */
	public ArrayList<HashMap<Object, Object>> executeSql(String strSql,
			HashMap<Integer, Object> prams) throws Exception {
		getConnection();
		// getConnection(_DRIVER,_URL,_USER_NA,_PASSWORD);
		pstmt = con.prepareStatement(strSql);
		setParamet(pstmt, prams);
		logger.info("###############::执行SQL语句操作(查询数据):" + strSql);
		rs = pstmt.executeQuery();
		con.commit();
		if (null != rs) {
			return convertResultSetToArrayList(rs);
		}
		return null;
	}

	/**
	 * 执行存储过程(查询数据 无参数)
	 * 
	 * @param procName
	 *            存储过程名称
	 * @return 数组列表对象
	 * @throws Exception
	 */
	public ArrayList<HashMap<Object, Object>> executeProcedureQuery(
			String procName) throws Exception {
		getConnection();// 获取连接
		String callStr = "{call " + procName + "}";// 构造执行存储过程的sql指令
		CallableStatement cs = con.prepareCall(callStr);
		logger.info("###############::执行存储过程(查询数据):" + procName);
		rs = cs.executeQuery();
		con.commit();
		cs.close();
		close_DB_Object();
		return convertResultSetToArrayList(rs);
	}

	/**
	 * 执行存储过程(查询数据,带参数)返回结果集合
	 * 
	 * @param procName
	 *            存储过程名称
	 * @param parameters
	 *            参数对象数组
	 * @param al
	 *            数组列表对象
	 * @return 数组列表对象
	 * @throws Exception
	 */
	public ArrayList<HashMap<Object, Object>> executeProcedureQuery(
			String procName, Object[] parameters) throws Exception {
		int parameterPoint = 0;
		// 获取存储过程信息列表集合
		ArrayList<HashMap<Object, Object>> procedureInfo = getProcedureInfo(procName);
		// 获取存储过程的完全名称
		String procedureCallName = getProcedureCallName(procName,parameters.length);
		// 获取连接对象
		getConnection();
		// 初始化 存储过程 执行对象
		CallableStatement cs = con.prepareCall(procedureCallName);
		// 参数下标变量
		int index = 0;
		// 获取 存储过程信息列表集合的 迭代器 对象
		Iterator<HashMap<Object, Object>> iter = procedureInfo.iterator();
		// 遍历存储过程信息列表集合
		while (iter.hasNext()) {
			HashMap<Object, Object> hm = iter.next();

			parameterPoint++;
			// 如果参数是输入参数 way = 0
			if (hm.get("WAY").equals("0")) {
				// 设置参数到cs
				cs.setObject(parameterPoint, parameters[index]);
				// 参数下标+1
				index++;
			}
		}
		// 释放这个对象,做为第二次使用
		procedureInfo = null;
		logger.info("###############::执行存储过程(查询数据):::::" + procedureCallName);
		rs = cs.executeQuery();
		con.commit();
		procedureInfo = convertResultSetToArrayList(rs);
		cs.close();
		close_DB_Object();
		return procedureInfo;

	}

	/**
	 * 执行存储过程(更新,查询数据[简单查询、非纪录集],返回输出参数[非纪录集])
	 * 
	 * @param procName
	 *            存储过程名称
	 * @param parameters
	 *            参数对象数组
	 * @param os
	 *            输出参数对象数组
	 * @return 输出参数对象数组
	 * @throws Exception
	 */
	public Object[] executeProcedureUpdate(String procName, Object[] parameters)
			throws Exception {
		logger.info("------------------------------------------------------------------------------------------------------");
		logger.info(" Run --> executeProcedureUpdate ##############   正在执行 存储过程: " + procName +"   ##############");
		CallableStatement cs = null;
		Object []returnVal = null;
		try {
		// 获取 存储过程 调用全名
		String fullPCallName = getProcedureCallName(procName,parameters.length);
		logger.info(" Run --> executeProcedureUpdate #   存储过程命令: " + fullPCallName +"   #");
		//获取存储过程参数信息
		ArrayList<HashMap<Object, Object>> p_Call_Info_List = getProcedureInfo(procName);
		//获取连接
		getConnection();
		//创建 存储过程 执行对象
		cs = con.prepareCall(fullPCallName);
		//数组下标
		int index = 1;
		//输出参数下标 纪录
        ArrayList<Integer> outPutIndexList = new ArrayList<Integer>();
        logger.info(" Run --> executeProcedureUpdate #   参数个数是: " + parameters.length +"   #");
		for(HashMap<Object,Object> tempHash:p_Call_Info_List)
		{
			if("0".equals(tempHash.get("WAY")))
		    {
				//设置输入参数
				cs.setObject(index, parameters[index-1]);
				logger.info(" Run --> executeProcedureUpdate #   输入 Input: 编号:" + index +" 值: "+parameters[index-1]+" 类型: "+parameters[index-1].getClass()+"   #");
			}
			else
			{
				//注册输出参数
				cs.registerOutParameter(index, getDataType(tempHash.get("TYPENAME").toString()));
				//纪录输出参数的下标
				outPutIndexList.add(index);
				logger.info(" Run --> executeProcedureUpdate #   输出 OutPut: 编号:" + index +" 值: "+parameters[index-1]+" 类型: "+parameters[index-1].getClass()+"   #");
			}
			index++;
		}
		logger.info(" Run --> executeProcedureUpdate #   参数设置完毕,正在执行中 ... :   #");
		
		//-------------------- 执行 -----------------
		if(!cs.execute())
		{
			returnVal = new Object[outPutIndexList.size()];
			logger.info(" Run --> executeProcedureUpdate #   执行成功! :   #");
			//取输 出参数的 返回值
			for(int i = 0 ;i<outPutIndexList.size();i++)
			{
				returnVal[i] = cs.getObject(outPutIndexList.get(i));
				logger.info(" Run --> executeProcedureUpdate #   返回值 "+(i+1)+" "+returnVal[i]+"   #");
			}
			con.commit();//提交
		}
		} catch (Exception e) {
			logger.info(" Run --> executeProcedureUpdate #   执行失败!事务回滚中... :   #");
			con.rollback();
			throw e;
		} 
		logger.info("------------------------------------------------------------------------------------------------------");
		return returnVal;
	}

	/** ****************************************************************************************** */
	/**
	 * ********************************* 小工具
	 * ************************************************
	 */
	/** ****************************************************************************************** */

	/**
	 * 关闭数据对象
	 */
	public void close_DB_Object() {
		logger.info("###############close:::::关闭连接对象,语句对象,记录集对象");
		if (null != rs) {
			try {
				rs.close();
			} catch (SQLException ex) {
				rs = null;
			}
		}
		if (null != stmt) {
			try {
				stmt.close();
			} catch (SQLException ex) {
				stmt = null;
			}
		}
		if (null != pstmt) {
			try {
				pstmt.close();
			} catch (SQLException ex) {
				pstmt = null;
			}
		}
		if (con != null) {
			dcm.freeConnection("mysql", con);
		}
	}


	/**
	 * 设置Sql 指令参数
	 * 
	 * @param p_stmt
	 *            PreparedStatement
	 * @param pramets
	 *            HashMap
	 */
	private PreparedStatement setParamet(PreparedStatement p_stmt,
			HashMap<Integer, Object> pramets) throws ClassNotFoundException,
			SQLException {
		// 如果参数为空
		if (null != pramets) {
			// 如果参数个数为0
			if (0 <= pramets.size()) {
				for (int i = 1; i <= pramets.size(); i++) {
					try {
						// 字符类型 String
						if (pramets.get(i).getClass() == Class
								.forName("java.lang.String")) {
							p_stmt.setString(i, pramets.get(i).toString());
						}
						// 日期类型 Date
						if (pramets.get(i).getClass() == Class
								.forName("java.sql.Date")) {
							p_stmt.setDate(i, java.sql.Date.valueOf(pramets
									.get(i).toString()));
						}
						// 布尔类型 Boolean
						if (pramets.get(i).getClass() == Class
								.forName("java.lang.Boolean")) {
							p_stmt.setBoolean(i, (Boolean) (pramets.get(i)));
						}
						// 整型 int
						if (pramets.get(i).getClass() == Class
								.forName("java.lang.Integer")) {
							p_stmt.setInt(i, (Integer) pramets.get(i));
						}
						// 浮点 float
						if (pramets.get(i).getClass() == Class
								.forName("java.lang.Float")) {
							p_stmt.setFloat(i, (Float) pramets.get(i));
						}
						// 双精度型 double
						if (pramets.get(i).getClass() == Class
								.forName("java.lang.Double")) {
							p_stmt.setDouble(i, (Double) pramets.get(i));
						}

					} catch (ClassNotFoundException ex) {
						throw ex;
					} catch (SQLException ex) {
						throw ex;
					}
				}
			}
		}
		return p_stmt;
	}

	/**
	 * 转换记录集对象为数组列表对象
	 * 
	 * @param rs
	 *            纪录集合对象
	 * @return 数组列表对象
	 * @throws Exception
	 */
	private ArrayList<HashMap<Object, Object>> convertResultSetToArrayList(
			ResultSet rs) throws Exception {
		logger.info("###############::转换记录集对象为数组列表对象");
		// 获取rs 集合信息对象
		ResultSetMetaData rsmd = rs.getMetaData();
		// 创建数组列表集合对象
		ArrayList<HashMap<Object, Object>> tempList = new ArrayList<HashMap<Object, Object>>();
		HashMap<Object, Object> tempHash = null;
		// 填充数组列表集合
		while (rs.next()) {
			// 创建键值对集合对象
			tempHash = new HashMap<Object, Object>();
			for (int i = 0; i < rsmd.getColumnCount(); i++) {
				// 遍历每列数据,以键值形式存在对象tempHash中
				tempHash.put(rsmd.getColumnName(i + 1).toUpperCase(), rs
						.getString(rsmd.getColumnName(i + 1)));
			}
			// 第一个键值对,存储在tempList列表集合对象中
			tempList.add(tempHash);
		}
		close_DB_Object();// 关闭相关链接
		return tempList;// 返回填充完毕的数组列表集合对象
	}

	/**
	 * 从数据库得到存储过程信息
	 * 
	 * @param procName
	 *            存储过程名称
	 * @return 数组列表对象
	 * @throws Exception
	 */
	private ArrayList<HashMap<Object, Object>> getProcedureInfo(String procName)
			throws Exception {
		return this.executeSql("select Syscolumns.isoutparam as Way,systypes.name as TypeName from sysobjects,syscolumns,systypes where systypes.xtype=syscolumns.xtype and syscolumns.id=sysobjects.id and sysobjects.name='"
				+ procName + "' order by Syscolumns.isoutparam");
	}

	/**
	 * 从数据库得到存储过程参数个数
	 * 
	 * @param procName
	 *            存储过程名称
	 * @return 数组列表对象
	 * @throws Exception
	 */
	@SuppressWarnings("unused")
	private int getParametersCount(String procName) throws Exception {
		int returnVal = 0;
		for (HashMap<Object, Object> tempHas : this
				.executeSql("select count(*) as RowsCount from sysobjects,syscolumns,systypes where systypes.xtype=syscolumns.xtype and syscolumns.id=sysobjects.id and sysobjects.name='"
						+ procName + "'")) {
			returnVal = Integer.parseInt(tempHas.get("ROWSCOUNT").toString());
		}
		return returnVal;
	}

	/**
	 * 得到调用存储过程的全名
	 * 
	 * @param procName
	 *            存储过程名称
	 * @return 调用存储过程的全名
	 * @throws Exception
	 */
	private String getProcedureCallName(String procName, int prametCount)
			throws Exception {
		String procedureCallName = "{call " + procName;
		for (int i = 0; i < prametCount; i++) {
			if (0 == i) {
				procedureCallName = procedureCallName + "(?";
			}
			if (0 != i) {
				procedureCallName = procedureCallName + ",?";
			}
		}
		procedureCallName = procedureCallName + ")}";
		return procedureCallName;
	}

	/**
	 * 得到数据类型的整型值
	 * 
	 * @param typeName
	 *            类型名称
	 * @return 数据类型的整型值
	 */
	private int getDataType(String typeName) {
		if (typeName.equals("varchar"))
			return Types.VARCHAR;
		if (typeName.equals("int"))
			return Types.INTEGER;
		if (typeName.equals("bit"))
			return Types.BIT;
		if (typeName.equals("float"))
			return Types.FLOAT;
		return 0;
	}

	// 设置驱动路径
	@SuppressWarnings("static-access")
	public void set_DRIVER(String _DRIVER) {
		this._DRIVER = _DRIVER;
	}

	// 设置数据库密码
	@SuppressWarnings("static-access")
	public void set_PASSWORD(String _PASSWORD) {
		this._PASSWORD = _PASSWORD;
	}

	// 设置数据库连接字符串
	@SuppressWarnings("static-access")
	public void set_URL(String _URL) {
		this._URL = _URL;
	}

	// 设置数据库用户名
	@SuppressWarnings("static-access")
	public void set_USER_NA(String _USER_NA) {
		this._USER_NA = _USER_NA;
	}

}

分享到:
评论
4 楼 qdongl 2012-02-08  
不错,继续学习
3 楼 ray111 2011-04-13  
缺少DBConnectionManager类?
2 楼 wu19880125 2008-12-23  
写的很好,很规范,要是将上面的分开写做几个专题更好。那样会更明确
1 楼 yangzhibin_java 2008-08-27  
这个看过了,还有高档点了

相关推荐

    java开源包4

    BoneCP 是一个高性能的开源java数据库连接池实现库。它的设计初衷就是为了提高数据库连接池的性能,根据某些测试数据发现,BoneCP是最快的连接池。BoneCP很小,只有四十几K(运行时需要slf4j和guava的支持,这二者加...

    JAVA上百实例源码以及开源项目源代码

     用JAVA开发的一个小型的目录监视系统,系统会每5秒自动扫描一次需要监视的目录,可以用来监视目录中文件大小及文件增减数目的变化。 Java日期选择控件完整源代码 14个目标文件 内容索引:JAVA源码,系统相关,日历,...

    java开源包11

    BoneCP 是一个高性能的开源java数据库连接池实现库。它的设计初衷就是为了提高数据库连接池的性能,根据某些测试数据发现,BoneCP是最快的连接池。BoneCP很小,只有四十几K(运行时需要slf4j和guava的支持,这二者加...

    java开源包6

    BoneCP 是一个高性能的开源java数据库连接池实现库。它的设计初衷就是为了提高数据库连接池的性能,根据某些测试数据发现,BoneCP是最快的连接池。BoneCP很小,只有四十几K(运行时需要slf4j和guava的支持,这二者加...

    java开源包9

    BoneCP 是一个高性能的开源java数据库连接池实现库。它的设计初衷就是为了提高数据库连接池的性能,根据某些测试数据发现,BoneCP是最快的连接池。BoneCP很小,只有四十几K(运行时需要slf4j和guava的支持,这二者加...

    JAVA上百实例源码以及开源项目

     用JAVA开发的一个小型的目录监视系统,系统会每5秒自动扫描一次需要监视的目录,可以用来监视目录中文件大小及文件增减数目的变化。 Java日期选择控件完整源代码 14个目标文件 内容索引:JAVA源码,系统相关,日历,...

    java开源包101

    BoneCP 是一个高性能的开源java数据库连接池实现库。它的设计初衷就是为了提高数据库连接池的性能,根据某些测试数据发现,BoneCP是最快的连接池。BoneCP很小,只有四十几K(运行时需要slf4j和guava的支持,这二者加...

    java开源包5

    BoneCP 是一个高性能的开源java数据库连接池实现库。它的设计初衷就是为了提高数据库连接池的性能,根据某些测试数据发现,BoneCP是最快的连接池。BoneCP很小,只有四十几K(运行时需要slf4j和guava的支持,这二者加...

    java开源包8

    BoneCP 是一个高性能的开源java数据库连接池实现库。它的设计初衷就是为了提高数据库连接池的性能,根据某些测试数据发现,BoneCP是最快的连接池。BoneCP很小,只有四十几K(运行时需要slf4j和guava的支持,这二者加...

    java开源包10

    BoneCP 是一个高性能的开源java数据库连接池实现库。它的设计初衷就是为了提高数据库连接池的性能,根据某些测试数据发现,BoneCP是最快的连接池。BoneCP很小,只有四十几K(运行时需要slf4j和guava的支持,这二者加...

    java开源包1

    BoneCP 是一个高性能的开源java数据库连接池实现库。它的设计初衷就是为了提高数据库连接池的性能,根据某些测试数据发现,BoneCP是最快的连接池。BoneCP很小,只有四十几K(运行时需要slf4j和guava的支持,这二者加...

    java开源包3

    BoneCP 是一个高性能的开源java数据库连接池实现库。它的设计初衷就是为了提高数据库连接池的性能,根据某些测试数据发现,BoneCP是最快的连接池。BoneCP很小,只有四十几K(运行时需要slf4j和guava的支持,这二者加...

    java开源包2

    BoneCP 是一个高性能的开源java数据库连接池实现库。它的设计初衷就是为了提高数据库连接池的性能,根据某些测试数据发现,BoneCP是最快的连接池。BoneCP很小,只有四十几K(运行时需要slf4j和guava的支持,这二者加...

    java开源包7

    BoneCP 是一个高性能的开源java数据库连接池实现库。它的设计初衷就是为了提高数据库连接池的性能,根据某些测试数据发现,BoneCP是最快的连接池。BoneCP很小,只有四十几K(运行时需要slf4j和guava的支持,这二者加...

    Java资源包01

    BoneCP 是一个高性能的开源java数据库连接池实现库。它的设计初衷就是为了提高数据库连接池的性能,根据某些测试数据发现,BoneCP是最快的连接池。BoneCP很小,只有四十几K(运行时需要slf4j和guava的支持,这二者加...

    db-utils:数据库实用程序

    用于连接管理的单例BaseDAO:用于在数据库中存储和检索可识别对象的通用抽象类待办事项DBQueryBuilder仅支持CREATE和SELECT子句,并不意味着它是完整的。用法DBQueryBuilder SQLClause createClause = ...

    Java开发技术大全 电子版

    Java开发技术大全 电子版 第1篇Java基础知识入门. 第1章Java的开发运行环境2 1.1Java的运行环境与虚拟机2 1.2Java的开发环境4 1.2.1JDK的安装4 1.2.2如何设置系统环境变量6 1.2.3编译命令的使用8 1.2.4解释...

    基于JAVA的模拟ATM系统的设计与实现【文献综述】.pdf

    文献综述 计算机科学与技术 基于 JAVA 的模拟 ATM 系统的设计与实现 ATM 是 Automatic Teller Machine 的缩写,即自动柜员机。它是有计算机控制的持 卡人自我服务型的金融专用设备。 ATM 机可以向持卡人提供提款、...

Global site tag (gtag.js) - Google Analytics