精华内容
下载资源
问答
  • 浅浅地封装 JDBC

    千次阅读 2012-08-23 13:30:39
    数据库向来是个“老大难”问题。...首先我最开始接触的是 FoxBase,那时是在学校的数据库课程上,还有学校日常的考勤工作,我也有幸参与了,都是用 FoxBase/FoxPro 进行管理的。现在语法已经忘光了

    写在数据库重构前面的话——说说我之前认识的数据库

    数据库向来是个“老大难”问题。好的数据库设计与好的数据库程序密切相关,一个逻辑清楚、管理维护方便的程序往往离不开优秀的数据库表设计。本人之前就是基础不牢,对这个问题认识不够,忽视了数据库表设计,导致了后来程序层(ASP/JSP)怎么写都不顺当——好生烦恼!究其原因,就是把关系型的数据库的问题简单化、想当然。如今是该正视问题的时候了!

    首先我最开始接触的是 FoxBase,那时是在学校的数据库课程上,还有学校日常的考勤工作,我也有幸参与了,都是用 FoxBase/FoxPro 进行管理的。现在语法已经忘光了,感觉那时候学得容易,也直观。学校里也开了一课 Access,但老师讲得敷衍(确实如此,不是我不尊重老师)。

    后来自学 ASP,也涉及到 SQL。于是简单学习了一下 SELECT/UPDATE/INSERT INTO 等这样的操作语句。因为是自学,没有经过数据库理论系统的学习。我们知道,数据库尤其是关系型数据库是一门独立的学科,在计算机课程中显得十分重要。这门课除 SQL 操作语句之外,其中相当于知识点都是偏理论和数学的,例如关系代数、范式等。我惧怕这些知识,因此也没有经过扎实的学习,而是参考了一个简单 ASP 新闻发布系统,参考了里面 Access 的设计。不得不说,我可作为一个反面例子,说明这样求学的心态是如何浮躁的。

    有一点还记得的,就是学会了 SQL Join 语句之后,好像立刻获得什么重大成就那样子,马上兴奋异常。不过确实感觉到了 Join 的强大,可以做以前做不了的很多事情——窃以为,这就是“自学”的好处吧,容易获得成就感,哪怕是一点一滴的,且自我感觉良好。

    后学我花时间在语言方面比较多,也没怎么关注数据库。倒是接触 Java/Hibernate 之际了解了什么是 ORM,知道啥是“阻抗不匹配”。

    在了 ROR 框架的时候,发现其所使用 ActiveRecord 不错,感觉理解起来挺容易。于是尝试自己写  ActiveRecord 的实现,还是用 ASP/Jscript 的。后来归纳总结,所谓 ActiveRecord 属于数据库模式中的一种,所谓 belongsTo、hasMany 无非就是原来的“一对多”、“多对一”之类的,没有跳出数据库的“五指山”——而那时的我盲目跟风,只知其一不知其二。

    再后来业界“NoSQL”的蹿红,使得我更有理由“鄙视”关系型数据库,觉得反正用不用 MySQL、SQL Server 都可以——唉,无知呐。

    ORM 会给人一种错觉:使人觉得数据库的问题可以在语言层面得到解决。我为此开始了“孜孜不倦”尝试,从 ASP 到 JSP,总以为可以封装好 SQL。然而,我却不是赞成使用 Hibernate 这些框架,甚至轻视!感觉其中无非就是 SQL 语句的拼凑——要不我自己来吧!我甚至到了一种地步,把面向对象的“继承”生搬硬套到表设计中!那,当然是要“闹笑话”的——唉!

    “表设计很重要”,这是我一个写后台的同事说的。我的表那么多冗余、设计的那么不自然,——为什么我还能不以为然、还能容忍这种情况存在?难道 Java 层面就可以把 SQL 完全搞定??

    总之,我一再反思,希望找出问题的根源。

    目标

    请注意:该文章已过时,请留意最新版本,相关资源在下一节《Bigfoot 数据库 CRUD 举例 》

    众所周知,通过 JDBC 我们可以向数据库询问和写入数据(增、删、改、查)。但直接使用 JDBC 开发效率不高,于是我们想想怎么封装一层吧!

    大目标:轻量级,使数据库操作更简单、更顺手、更纯粹!

    目标有大必有小。小的具体目标如下:

    • 使用 Tomcat 自带的数据库连接池 JDBC Pool。
    • 提倡先写 SQL,看是否 OK,再封装。实际上,无论什么业务,都是产生 SQL 然后交给 JDBC 执行的,所以 SQL 是最终的“编译”结果。
    • 封装是什么封装方法?这是一种半自动化 ORM,既非手工的 SQL 字符串拼凑,而是提供一种简单的方法维护 SQL。不用写直接写 SQL,而是 Java + SQL。
    • 即使封装 SQL 语句,也不要过渡封装。
    • 采用 Map 为记录载体
    • 与 JS 引擎 Rhino 无缝对接,用 JS 定义一些方法
    • 抛弃 Java POJO,以 JS 定义实体,适合前后台所使用

    缺点是:一、通用性不会很强,只限于当前框架所使用,约定的关系较多,耦合紧;二、当前只针对 MySQL/SQLite。

    基础模块

    之所以有这个基础模块,其实是排除了 DAO(Data Access Object) 所剩余的 SQL 包下面的内容。关于 DAO 模块,后面会论述。

    Base

    Base 围绕下面几个常见的问题而设,也就是把一些共性的特征给抽象出来,是底层的类。Base 是一个抽象类(Abstract Class),虽然他们之间没有必然的关联关系,但还是形成了这个类,属于“自底向上”的设计。使用这个抽象类有下面介绍的 Reader、Writer。

    具体流程参见最新源码:http://code.taobao.org/p/bigfoot_v2/src/java/com/ajaxjs/framework/sql/Base.java

    一言蔽之,Connection 对象,每个数据库首先都得创建这个对象——所以是必须的;TableName,不言而喻,怎么能不知道表名?;id 是指列名具体是什么?id 还是 uid,要说清楚;最后的资源释放操作,是关于 Connection、ResultSet 和 Statement 的。于是围绕上述几点,有下面的方法。

    这些字段均是 getter/setter。Conn 就特殊点,因为初始化数据库连接对象提供了若干重载方法,适应不同场合。针对 Conn 还有释放资源的方法。

    Result

    具体流程参见最新源码:http://code.taobao.org/p/bigfoot_v2/src/java/com/ajaxjs/framework/sql/Result.java

    数据,可定义为有意义的实体,它涉及事物的存在形式——通常不止一种形式,在数据库里是一种形式,在 Java 程序里是一种形式,在接口层面又是另外一种形式。针对查询操作,应该有专门的类保存数据的不同形式,有 JSON、Map 和 Map[],还有记录操作过的 SQL 语句,保存起来在日志文件中。Result 对象没有使用 getter/setter,而是直接使用对象的属性。结构如下。

    /**
     * 查询结果
     * @author Frank Cheung
     */
    public class Result {
    	/**
    	 * 返回前端的 JSON 字符串
    	 */
    	public String json = null;
    	
    	/**
    	 * 结果集合,复数,如果单数则是 jsArr.get(0)
    	 */
    	public NativeArray jsArr = null;
    	
    	/**
    	 * 执行过查询的那个 SQL 语句。可用于追溯
    	 */
    	public String sqlStatement = null;
    	
    	/**
    	 * 已转换为 Map 的结果,如果复数则是 null
    	 */
    	public Map<String, Object> result  = null;
    	
    	/**
    	 * 已转换为 Map 结果集合,如果单数则是 null
    	 */
    	public Map<String, Object>[] results = null;
    	
    	/**
    	 * 查询耗时
    	 */
    	public Date queryTime = null;
    }

    该类的设计充分符合了为方便 JSON 接口输出的原则(故内部也是用 Rhino 遍历 ResutSet,直接返回 JSON)。接口每次请求完毕,都会读取 Result.json 的结果。如果要在 Java 中作业务逻辑处理,则读取 Result.result/results 即可。

    SQL

    这是对 SQL 字符串语句的封装。我曾经为这个模块想到过的名字“SQL 编译器”,虽然有点贴切,但未免口气太大,与实际情况有点不符,想想这个名字还是算了。我的目标是这样的:应该是一种“半自动化 ORM”,既非手工的 SQL 字符串拼凑,而是提供一种简单的方法维护 SQL。不用写直接写 SQL,而是 Java + SQL。不是想代替 SQL,SQL 的作用仍非常重要的,此处只是用一种简单的方法封装 SQL 操作,不用直接写 SQL 那么辛苦。

    这个类的作用有点类似于 QueryRunner

    实际成果究竟怎么样?不妨先看看调用的例子,感觉下如何。

    例子如下(摘自单元测试)。

    String sqlStr;
    Sql sql = new Sql();
    sql.setTableName("User");
    sql.setSql("SELECT * FROM " + sql.getTableName());
    sqlStr = sql.query();
    assertEquals("SELECT * FROM User", sqlStr);
    assertEquals(sqlStr, sql.query()); // 没有其他条件,此时 应是一致的,
    
    // 排序
    sql.setOrderBy(String.format(" ORDER BY %s.id DESC", sql.getTableName()));
    sqlStr = sql.query();
    assertEquals("SELECT * FROM User ORDER BY User.id DESC", sqlStr);
    
    // 需要分页的
    sql.isPage  = true;
    sql.setLimit(3, 9);
    sqlStr = sql.query();
    assertEquals("SELECT * FROM User ORDER BY User.id DESC LIMIT 3, 9", sqlStr);
    
    // WHERE 条件查询
    sql.addWhere("id = 1000");
    sql.addWhere("isAdmin = true");
    sqlStr = sql.query();		
    assertEquals("SELECT * FROM User WHERE (id = 1000 AND isAdmin = true) ORDER BY User.id DESC LIMIT 3, 9", sqlStr);
    
    sql.addWhere_OR("name = 'Jack'");
    sql.addWhere_OR("name = 'Mike'");
    sqlStr = sql.query();
    assertEquals("SELECT * FROM User WHERE (id = 1000 AND isAdmin = true) AND (name = 'Jack' OR name = 'Mike') ORDER BY User.id DESC LIMIT 3, 9", sqlStr);
    
    // 求结果总数
    sqlStr = sql.findRowCountSql();
    assertEquals("SELECT COUNT(User.uid) AS count FROM User WHERE (id = 1000 AND isAdmin = true) AND (name = 'Jack' OR name = 'Mike')", sqlStr);
    System.out.println(sqlStr);

    如例子所示,SQL 类做的事情很简单。而且我规定 SQL 类只是返回 String 类型的 SQL 语句,皆是字符串的操作和输入、输出,不涉及的数据库执行等的其他问题,目的是令其专注。

    当前封装了 SQL 语句以下查询的功能:

    • WHERE
    • LIMIT
    • ORDER BY
    • Query Total AS Int

    此外,还有写操作的部分有待完善。

    Data Access Object

    DAO 实际上是一个接口,详情如下:

    import java.sql.Connection;
    import com.ajaxjs.sql.Result;
    import com.ajaxjs.sql.Sql;
    
    public interface DAO {
        public Connection getConn();
        public void setConn(Connection conn);
        public Result query(String sql, boolean isSolo);
        public int queryAsInt(String sql);
        public Result findById(Sql sqlInstance, String id);
        public Result findList(Sql sqlInstance, Integer start, Integer limit);
        public int executeUpdate(String sql);
        public int create(Map<String, Object> pair);
        public int update(Map<String, Object> pair, String id);
        public boolean delete(String uid);
    }
    

    DAO 的目的是为其他层提供访问数据库的服务。程序员无须知道其内部细节,就可以轻松调用数据库。

    具体流程参见最新源码:http://code.taobao.org/p/bigfoot_v2/src/java/com/ajaxjs/framework/sql/dao/DAO.java

    然而 DAO 内部是复杂的,代码层面是被划分为多个类的,有 Base、Reader、Writer。Base 就是前文提到过的抽象类。它们之间是逐层继承的。最终由 Writer 实现了 DAO 接口。从 DAO 往 Writer、Reader 看,是“自顶向下设计(from Up to Button design)”,也就是了实现了一大块的功能块,然后抽出哪些需要的形成一个接口,这样的设计比较灵活,显示了多态的威力,也比较吻合我的初衷。

    值得一提的是,Reader 和 Writer 可以分别单独使用。

    Reader

    Reader 为只读的数据库访问类。

    query()

    public Result query(String sql, boolean isSolo); 是通用方法,参数是 SELECT 语句,isSolo 是 boolean 参数,说明返回的结果是否单数。单行记录的是单数的意思,多数的意思就是多行记录。返回的结果保持 Result.result(单数)/ Result.results(多数)中。

    例子如下(摘自单元测试)。

    String sql = "SELECT * FROM news";
    
    Reader reader = new Reader(conn); // conn 是数据库连接对象
    Map<String, Object>[] list = reader.query(sql, false).results;
    assertNotNull(list);
    assertNotNull("总数:", list.length);
    
    int total = reader.queryAsInt("SELECT COUNT(name) AS total FROM " + tableName);
    assertNotNull("总数:", total);

    query() 方法内部就是封装了一些异常和是否有结果的判断,还有就是 Statement、ResultSet 对象的资源释放,并进行适当的类型转换。

    findById()

    public Result findById(Sql sql, String id) 对 query() 简单封装,SQL 语句的变化就是在后面加上 WHERE id = xxx,这是单数的查询。要求传入 Sql 对象 和 id 参数。

    WHERE id = xxx 中 id 列名是允许指定的。

    例子如下(摘自单元测试)。

    Sql sql = new Sql();
    sql.setTableName(tableName);
    Reader reader = new Reader(conn);
    Result result = reader.findById(sql, "49d09367-8678-4ecb-8921-d24320e11f96");
    
    assertNotNull(result.result);
    assertEquals(result.result.get("name"), "fooo");
    
    System.out.println(result.json);

    findList()

    public Result findList(Sql sql, Integer start, Integer limit) 读取列表(设置排序、是否需要分页)。如果不需要分页传入第二、第三个参数 null 即可。

    例子如下(摘自单元测试)。

    Sql sql = new Sql();
    sql.setTableName(tableName);
    Reader reader = new Reader(conn);
    Result result = reader.findList(sql, null, null);
    
    assertNotNull(result.results);
    //		assertEquals(result.results[0].get("name"), "fooo");
    
    System.out.println(result.json);

    findlList() 结合 Sql 进行操作。如果纯粹 SQL 字符串输入,建议使用 query() 方法(没有分页特性,自己处理)。

    Writer

    Writer 为写入的数据库访问类,大体可分为 create 和 update 两项操作。

    执行 SQL 语句

    简单的执行 create/update 操作,没什么好说的。

    例子如下(摘自单元测试)。

    Writer writer = new Writer(conn);
    
    int newlyId = writer.create("INSERT INTO news (name) VALUES ('hihi')");
    String msg = "插入一条新纪录(pure sql)";
    System.out.println(msg + newlyId);
    
    assertNotNull(msg, newlyId);

    newlyId 是影响的行数或者新建记录的 id。

    输入 Map/String[] pair/String[] columns、values

    这时候必须输入要操作的表名。

    例子如下(摘自单元测试)。

    Writer writer = new Writer(conn);
    writer.setTableName("news"); // 必须设置表名
    
    Map<String, Object> pair = new HashMap<String, Object>();
    pair.put("uid",  java.util.UUID.randomUUID().toString());
    pair.put("name", "fooo");
    
    String msg = "插入一条新纪录(Map 载体)";
    int newlyId = writer.create(pair);
    System.out.println(msg + newlyId);
    
    assertNotNull(msg, newlyId);

    也可以不同形式的 String[] pair/String[] columns、values。

    例子如下(摘自单元测试)。

    @Test
    public void createByArray(){
    	Writer writer = new Writer(conn);
    	writer.setTableName(ReaderTest.tableName);
    	
    	String[] pair = new String[]{
    		"name=bar", "content=my content"
    	};
    	
    	String msg = "插入一条新纪录(String[] pair)";
    	int newlyId = writer.create(pair);
    	System.out.println(msg + newlyId);
    	
    	assertNotNull(msg, newlyId);
    }
    
    @Test
    public void createByArrayCol_Val(){
    	Writer writer = new Writer(conn);
    	writer.setTableName(ReaderTest.tableName);
    	
    	String[] columns = new String[]{
    		"name", "content"
    	}, values = new String[]{
    		"foo", "your content"
    	};
    	
    	String msg = "插入一条新纪录(String[] columns, values)";
    	int newlyId = writer.create(columns, values);
    	System.out.println(msg + newlyId);
    	
    	assertNotNull(msg, newlyId);
    }

    Writer 满足了以下需求:

    • 传入 Map,生产 Update/INSERT 语句
    • 传入 Request,生成 Update/INSERT 语句
    • 普通 SQL 语句,执行 JDBC executeUpdate()
    • INSERT 返回新增 id

    设计 Writer 的时候考虑了一下几点,一、Statement 和 PreparedStatement 写法太不一样,要兼容的话太麻烦。干脆直接使用 PreparedStatement。仅保留 Statment 用于简单的 SQL 执行;二、有不同类型、不同结构的参数传入,种类繁复,最后通通转换为 Map<String, Object> 处理,不管传入的是什么类型的数据,数组还是 Map<String, String>[]。

    例子如下(摘自单元测试)。

    @Test
    public void updateByMap(){
    	Writer writer = new Writer(conn);
    	writer.setTableName(ReaderTest.tableName);
    	
    	Map<String, Object> pair = new HashMap<String, Object>();
    	pair.put("uid",  java.util.UUID.randomUUID().toString());
    	pair.put("name", "fooo");
    	
    	String msg = "更新一条新纪录(Map 载体)";
    	int newlyId = writer.update(pair, "49d09367-8678-4ecb-8921-d24320e11f96");
    	System.out.println(msg + newlyId);
    	
    	assertNotNull(msg, newlyId);
    }
    
    @Test
    public void updateByArray(){
    	Writer writer = new Writer(conn);
    	writer.setTableName(ReaderTest.tableName);
    	
    	String[] pair = new String[]{
    		"name=bar", "content=my content"
    	};
    	
    	String msg = "插入一条新纪录(String[] pair)";
    	int newlyId = writer.update(pair, "49d09367-8678-4ecb-8921-d24320e11f96");
    	System.out.println(msg + newlyId);
    	
    	assertNotNull(msg, newlyId);
    }
    
    @Test
    public void updateByArrayCol_Val(){
    	Writer writer = new Writer(conn);
    	writer.setTableName(ReaderTest.tableName);
    	
    	String[] columns = new String[]{
    		"name", "content"
    	}, values = new String[]{
    		"foo", "your content"
    	};
    	
    	String msg = "插入一条新纪录(String[] columns, values)";
    	int newlyId = writer.update(columns, values, "e83b3561-dcd9-45b6-a8a9-bc149c35f3eb");
    	System.out.println(msg + newlyId);
    	
    	assertNotNull(msg, newlyId);
    }

    删除记录

    删除就显得简单多了。

    Writer writer = new Writer(conn);
    writer.setTableName(ReaderTest.tableName);
    
    String uuid = java.util.UUID.randomUUID().toString();
    Map<String, Object> pair = new HashMap<String, Object>();
    pair.put("uid",  uuid);
    pair.put("name", "fooo");
    
    String msg = "插入一条新纪录(Map 载体)";
    int newlyId = writer.create(pair);
    assertNotNull(msg, newlyId);
    
    assertEquals(true, writer.delete(uuid));

    至此,SQL 这一大模块的介绍,就到此暂告一段落了。

    再计一个数据库工具类(以废弃)

    源码:

    https://code.google.com/p/naturaljs/source/browse/trunk/java/src/ajaxjs/sql/DAO.java

    一、数据库连接

    先说说构造器。

    /**
     * 连接数据库。
     * @param jdbcConn 数据库连接对象
     */
    public DAO(Connection jdbcConn){
    	this.jdbcConn = jdbcConn;
    	checkIfNull();
    }
    
    /**
     * 连接数据库(使用连接池)
     * @param path 连接池的路径
     */
    public DAO(String path){
    	this.jdbcConn = getConn(path);
    	checkIfNull();
    }
    开发者可以直接传入一个 Connection 类型的对象,或者 String。String 代表是什么呢?是连接池的路径。本框架通过 TOMCAT 7 连接池获取数据库连接,不需要其他第三方的 jar,使用起来比较方便、简单。于是 Strting path 是 META-INF 目录下 Content.xml 配置文件的连接路径,可以是 SQLite、MySQL 任意的 Tomcat 7 支持的连接池。详见 getConn(String path) 方法。
    /**
     * 创建数据库连接对象(通过 TOMCAT 7 连接池 获取数据库连接)
     * @param path Context.xml 配置文件中路径
     * @return
     */
    public static Connection getConn(String path){
    	Connection conn = null;
    	
    	try{
    		javax.naming.Context envContext  = (javax.naming.Context)new javax.naming.InitialContext().lookup("java:/comp/env"); 
    		javax.sql.DataSource ds = (javax.sql.DataSource)envContext.lookup(path); 
    //			 简写方式
    //			javax.sql.DataSource ds = (javax.sql.DataSource)new javax.naming.InitialContext().lookup("java:/comp/env/jdbc/derby"); 
    		
    		conn = ds.getConnection(); 
    		
    		System.out.println("数据库连接成功:" + conn);
    	}catch(SQLException e){
    		System.out.println("数据库连接失败!"); 
    		e.printStackTrace();
    	}catch (javax.naming.NamingException e) {
    		System.out.println("读取数据源的配置文件失败,请检查 Tomcat 连接池配置"); 
    		e.printStackTrace();
    	}
    	
    	return conn;
    }
    当然,框架中还保留传统的 JDBC 连接方式,如 jdbc:mysql://linux-db-bjzwNew2.xincache.cn:3306/net25923067?user=net22963167&password=2crFxx0n0x。下面是重载方法。
    /**
     * 创建数据库连接对象(传统方式)
     * @param driver e.g "com.mysql.jdbc.Driver"
     * @param jdbcUrl e.g "jdbc:mysql://linux-db-bjzwNew2.xincache.cn:3306/net25923067?user=net22963167&password=2crFxx0n0x"
     * @param props
     * @return
     */
    public static Connection getConn(String driver, String jdbcUrl, java.util.Properties props){
    	Connection conn = null;
    	
    	try {
    		Class.forName(driver);
    	} catch (ClassNotFoundException e) {
    		System.out.println("创建数据库连接失败,请检查是否安装对应的 Driver"); 
    		e.printStackTrace();
    	}
    	
    	try {
    		if(props == null){
    			conn = DriverManager.getConnection(jdbcUrl);
    		}else{
    			conn = DriverManager.getConnection(jdbcUrl, props);
    		}
    		System.out.println("数据库连接成功:" + conn);
    	} catch (SQLException e) {
    		System.out.println("数据库连接失败!"); 
    		e.printStackTrace();
    	}
    	
    	return conn;
    }
    关闭数据库连接也有两个重载的方法。
    /**
     * 关闭数据库连接
     */
    public void closeConnection(Connection jdbcConn){
    	try {
    		jdbcConn.close();
    	} catch (SQLException e) {
    		e.printStackTrace();
    	}
    }
    
    /**
     * 关闭数据库连接
     */
    public void closeConnection(){
    	try {
    		this.jdbcConn.close();
    	} catch (SQLException e) {
    		e.printStackTrace();
    	}
    }

    开发者需要手动关闭数据库连接。

    查询操作

    查询无非返回期待的数据和特定的类型。这里考虑几种使用情况:一、查询 ResultSet;二、任意的查询操作(传入回调);三、查询总记录路数(即 SQL Count() 函数),分别如下方法签名。

    /**
     * SQL 的 SELECT 查询,返回 RS 结果集合
     * @param sql
     * @return
     */
    public ResultSet query(String sql);
    
    /**
     * 支持回调的查询
     * @param sql
     * @param cb
     * @return
     */
    public Object query(String sql, Callback cb);
    
    /**
     * 用于记录总数的返回
     * @param sql
     * @param isCount
     * @return
     */
    public int query(String sql, boolean isCount);

    它们都调用这个真实的方法。
    /**
     * SQL 的 SELECT 查询,既返回 RS 结果集合,也可以返回 SELECT COUNT(id) AS total 的总数。
     * 这是读操作的查询。
     * @param {String} sql SELECT语句
     * @param {Booelan} isCount 是否返回总数
     * @returns {Object} 可变返回值,既可是 RS 结果集,也可以是 int 总数,使用强类型转换获取真实类型。
     */
    public Object query(String sql, boolean isCount, Callback cb){
    	Object arr = null;
    	Statement statement = null;
    	ResultSet resultset = null;
    			
    	System.out.println("将要查询的 SQL 为:" + sql);
    	
    	try{
    		statement = jdbcConn.createStatement();
    		resultset = statement.executeQuery(sql);
    		
    		if(isCount){
    			arr = resultset.isBeforeFirst() ? resultset.getInt(1) : null;
    			// cover to js number, but it doesn't work here.
    //				arr = jsEngine.call("Number", arr);
    		}else{
    			// 如果没有符合的记录,返回 null 
    			if(resultset.isBeforeFirst() && cb != null){
    				//arr = jsEngine.call("getValue", resultset);
    				cb.doIt(resultset);
    			}
    		}
    		
    		if(isCount == false && cb == null){
    			// 返回 resultset
    			arr = resultset;
    		}
    		
    //			System.out.println("查询成功!:::");
    	}catch(Throwable e){
    		System.out.println("查询失败!:::" + sql);
    		e.printStackTrace();
    	}finally{
    		if(isCount == false && cb == null){
    			// 返回 resultset 不要释放 resultset
    			release(statement, null);
    		}else release(statement, resultset);
    	}
    	
    	return arr;
    }
    
    /**
     * SQL 的 SELECT 查询,返回 RS 结果集合(List类型)
     * @param connection
     * @param sql
     * @return
     */
    public List<Map<String, String>> queryAsList(String sql);
    
    其中,回调的方法是强大、灵活的方法。现在很多接口都是 JSON 接口,为此,可以把解析 JSON 的逻辑写进这个 Callback 中。
    /**
     * 返回 JSON 格式数据
     * @param sql
     * @param jsEngine
     * @return
     */
    public Object queryAsJSON(String sql, final JsRuntime jsEngine){
    	this.query(sql, false, new Callback(){
    		public Object doIt(ResultSet resultset){
    			getJSON_List(jsEngine.call("getValue", resultset));
    			return null;
    		}
    	});
    	return JSON_arr;
    }	
    此处使用了 Rhino JS 引擎 直接序列化的 ResultSet 对象。

    写操作

    数据库的写操作比较简单,考虑返回的情况主要有以下两种:一、创建 ok,返回新纪录的 id;二、修改 ok,返回影响的行数;这点我已经 SQL 语句的不同作出了识别,返回的是 int。

    /**
     * 这是写操作的查询。
     * @param {String} sql
     * @returns {Number} 返回影响的行数
     */
    public int executeUpdate(String sql){
    	Statement statement = null;
    	int rowNumber = 0;
    			
    	System.out.println("将要执行写操作, SQL 为:" + sql);
    	try{
    		statement = jdbcConn.createStatement();
    		rowNumber = statement.executeUpdate(sql);
    		
    		if(sql.contains("INSERT INTO")){
    			// 创建新纪录,返回id
    			ResultSet rs =statement.getGeneratedKeys();
                if (rs.next()) {  
                	rowNumber = rs.getInt(1);   
    //		                System.out.println("数据主键:" + id);   
                } 
    		}
    		
    //			System.out.println("写操作成功!:::");
    	}catch(Throwable e){
    		System.out.println("写操作失败!:::" + sql);
    		System.out.println(e.toString());
    		
    		e.printStackTrace();
    	}finally{
    		release(statement, null);
    	}
    	
    	return rowNumber;
    }
    写操作远比查询简单,而且在一个方法中兼顾了 UPDATE 或 INSERT INTO 的操作。

    释放资源

    最后是释放 statement 对象和 ResultSet 对象资源,以免不及时释放带来内存占用过多的问题。这里,我特意设计其为一个通用的静态方法,无论查询的、还是写的方法,都可以通用。并且,更重要的是,不需要像 Connection 那样手动释放资源(您不需要手动执行 release() 这个方法),一切由框架为您代劳。这也是我设计该 DAO 的目标之一。

    /**
     * 是否数据库资源
     * @param statement
     * @param resultset
     */
    private static void release(Statement statement, ResultSet resultset){
    	if(statement != null)
    		try {
    			statement.close();
    		} catch (SQLException e) {}
    	
    	if(resultset != null)
    		try {
    			resultset.close();
    		} catch (SQLException e) {}
    }

    附:经典的 JDBC 访问数据库方法

    String sql = "SELECT * FROM User WHERE name = ?";
    java.sql.Connection jdbcConn = ajaxjs.sql.DAO.getConn("jdbc/mysql_deploy");
    java.sql.PreparedStatement statement = null;
    java.sql.ResultSet rs = null;
    String password = null;
    
    try {
    	statement = jdbcConn.prepareStatement(sql);
    	statement.setString(1, UserName);
    	System.out.println("开始查询数据库");
    	rs = statement.executeQuery();
    	if(rs.first()){
    		
    		// 有此用户,获取其密码
    		password = rs.getString("password");
    		// 保存登录用户信息
    		User.put("name", rs.getString("name"));
    		User.put("id", ((Integer)rs.getInt("id")).toString());
    		
    		System.out.println("数据库获取用户信息成功!" + rs.getString("name"));
    		return password;
    	}else{
    		// 没有此用户
    		throw new NullPointerException("没有此用户:" + UserName);
    	}
    } catch (SQLException e) {
    	// TODO Auto-generated catch block
    	e.printStackTrace();
    	return null;
    } catch (NullPointerException e){
    	e.printStackTrace();
    	throw e;
    }finally{
    	try {
    		statement.close();
    		rs.close();
    		jdbcConn.close();
    	} catch (SQLException e) {
    		// TODO Auto-generated catch block
    		e.printStackTrace();
    	}
    }
    参见:


    Bigfoot 数据库 CRUD 举例

    概述

    Bigfoot DAO 旨在实现数据库的 CRUD 操作,目的是使用起来小巧、灵活、方便。使用 Bigfoot DAO 很简单,分三步实现。
    第一步:建立数据库连接,框架支持两种数据库连接方式

    1.   通过硬编码指定 JDBC Url
    2.   通过 Tomcat 自带的数据库连接池产生 DataSource,进而获取 Connection 对象

    如非测试,推荐使用第二种方法。

    第二步:在以下选项中决定适合 CRUD 的组合,大多数情况下,推荐使用 UUID 主键、非事务和使用 PrepareStatement

    • boolean isTransactionEnable;            // 是否启用事务
    • boolean isUsingPreparedStatment;    // 是否使用 PreparedStatment
    • boolean isRETURN_GENERATED_KEYS;     // insert 是否返回自增 id
    • boolean isQueryAsSingle = true;                   // 如果 query() 只返回一条记录,则自动转化为但单独记录

    第三步:创建 DBAccessImpl 对象,调用其方法

    DBAccessImpl 实现了 DBAccess 接口,该接口目的为 CRUD 而生。

    Result<Record> queryOne(String sql); // 查询单笔记录
    Result<PureList> queryList(QueryRunner qr); // 查询多行记录
    Result<PureList> queryList(String sql); // 查询多行记录
    Result<PagedList> queryList(QueryRunner qr, int start, int limit); // 查询多行记录并分页
    Result<CreateAction> insert(String tablename, IRecord data); // 插入记录,data 是 Map 的封装
    Result<UpdateAction> update(String tablename, IRecord data, String uid);// 更新记录,data 是 Map 的封装
    boolean delete(String tablename, String uid);// 指定表名和 id,删除一条记录
    

    下面应用举例。

    举例

    查询单行、多行记录

    DBAccess dao = new DBAccessImpl(conn);
    Result<Record> oneResult = dao.queryOne("SELECT * FROM news WHERE uid = 100");// 可以是任意复杂的查询语句
    Result<PureList> listResult = dao.queryList("SELECT * FROM news");

    Result 是结果集对象,其中泛型 Record 表示单个实体,PureList 表示列表,但是就是普通结构的列表。

    查询的实质是将任意查询结果 ResultSet 转化成 List<Map>。框架内提供了一个将每一条记录转换成一个 Map 对象的方法,通过循环语句将任意的查询结果 ResultSet 转换成 List<Map>类型的变量。

    查询单行、多行记录,使用 QueryRunner

    QueryRunner 是 SQL 语句字符串拼凑工具类,采用链式查询结构。

    DBAccess dao = new DBAccessImpl(conn);
    Result<PureList> result;
    result = dao.queryList(new QueryRunner().select("uid").from("news"));
    assertNotNull(result);
    result = dao.queryList(new QueryRunner().select("uid").from("news").limit(0,  3));
    assertNotNull(result);
    result = dao.queryList(new QueryRunner().select("uid").from("news").limit(0,  3).orderBy("uid", "desc"));
    System.out.println(result.json); // 返回 json
    assertNotNull(result);
    

    如上所示,演示了 SQL 的分页和排序功能,并直接提供 JSON 返回的能力。

    插入一条记录

    DBAccessImpl dao = new DBAccessImpl(conn);
    dao.setUsingPreparedStatment(true);
    MapHelper<String, IValue> map = new MapHelper<>();
    map.put("uid", new Value(12345));
    map.put("name", new Value("Roooss"));
    dao.insert("news", map);
    IRecord 是封装过 Map 的自定义类型,实现类为 MapHelper。只有键名等于表的字段名的数据才会插入到表中。

    更新一条记录

    DBAccessImpl dao = new DBAccessImpl(conn);
    dao.setUsingPreparedStatment(true);
    MapHelper<String, IValue> map = new MapHelper<>();
    map.put("name", new Value("jack"));
    dao.update("news", map, "12345");
    插入或更新一组记录
    请查询 public SaveAction save(String[] sqls, IRecord[] data) throws SQLException 文档,这里不再赘述了。

    删除记录

    DBAccessImpl dao = new DBAccessImpl(conn);
    boolean isDeleted = dao.delete("news", "1000"); // 表名、 uid

    约定

    归纳一些基本的约定

    • 在 Map 变量(也就是封装过的 IRecord 类型)中,只有键名等于表的字段名的数据才会插入到表中。
    • 若无结果集,不返回零长度 List 对象,而是返回 null
    • 若无单个记录,不返回零长度 Map 对象,而是返回 null
    • 零长度字符串存入数据库中将保存为 null
    • DAO 通常会捕获了 SQLException 异常,不需要用户处理异常。

    一点小技巧

    最后,就是介绍大家一个我在框架内比较偷懒的办法。不论是查询,还是插入、更新记录,均使用 HashMap 对象作为一条记录的载体。这个 HashMap 对象的键名是表中的字段名,或是字段的别名,键值为字段值,键值的类型是字段所对应的 JDBC API 的 Java 类。针对数据库的操作,如:插入、更新、删除、查询,该方法会将所传递的 Map 型参数转化成 SQL 语句的组成部分,自动生成完整的标准的 SQL 语句,供 JDBC AP I调用。

    实际上,Map 这种方法一般封装过 JDBC 的库多少都会支持的,并不是什么鲜为人知的秘密啦~


    展开全文
  • 不同的协议对数据包有不同的称谓,在传输叫做段(segment),在网络叫做数据报(datagram),在链路叫做帧(frame)加上MAC头,加完后再加上一个FCS校验组成数据帧,就封装完成了,然后在物理通过Bit来...

    一、图形描述

    (1)OSI七层模型
    这里写图片描述

    这里写图片描述

    (2)TCP/IP五层模型的协议

    这里写图片描述

    (3)数据封装

    不同的协议层对数据包有不同的称谓,在传输层叫做(segment),在网络层叫做数据报(datagram),在链路层叫做(frame)加上MAC头,加完后再加上一个FCS校验组成数据帧,就封装完成了,然后在物理层通过Bit来传输。发到传输介质上,到达目的主机后每层协议再剥掉相应的首部,最后将应用层数据交给应用程序处理。

     

    二、TCP/UDP协议

    TCP (Transmission Control Protocol)和UDP(User Datagram Protocol)协议属于传输层协议。其中TCP提供IP环境下的数据可靠传输,它提供的服务包括数据流传送、可靠性、有效流控、全双工操作和多路复 用。通过面向连接、端到端和可靠的数据包发送。通俗说,它是事先为所发送的数据开辟出连接好的通道,然后再进行数据发送;而UDP则不为IP提供可靠性、 流控或差错恢复功能。一般来说,TCP对应的是可靠性要求高的应用,而UDP对应的则是可靠性要求低、传输经济的应用。TCP支持的应用协议主要 有:Telnet、FTP、SMTP等;UDP支持的应用层协议主要有:NFS(网络文件系统)、SNMP(简单网络管理协议)、DNS(主域名称系 统)、TFTP(通用文件传输协议)等.
    TCP/IP协议与低层的数据链路层和物理层无关,这也是TCP/IP的重要特点

    三、OSI的基本概念
    OSI是Open System Interconnect的缩写,意为开放式系统互联。
    OSI七层参考模型的各个层次的划分遵循下列原则:
    1、同一层中的各网络节点都有相同的层次结构,具有同样的功能。
    2、同一节点内相邻层之间通过接口(可以是逻辑接口)进行通信。
    3、七层结构中的每一层使用下一层提供的服务,并且向其上层提供服务。
    4、不同节点的同等层按照协议实现对等层之间的通信。

    第一层:物理层(PhysicalLayer)
    规定通信设备的机械的、电气的、功能的和过程的特性,用以建立、维护和拆除物理链路连接。具体地讲,机械 特性规定了网络连接时所需接插件的规格尺寸、引脚数量和排列情况等;电气特性规定了在物理连接上传输bit流时线路上信号电平的大小、阻抗匹配、传输速率 距离限制等;功能特性是指对各个信号先分配确切的信号含义,即定义了DTE和DCE之间各个线路的功能;规程特性定义了利用信号线进行bit流传输的一组 操作规程,是指在物理连接的建立、维护、交换信息是,DTE和DCE双放在各电路上的动作系列。在这一层,数据的单位称为比特(bit)。属于物理层定义的典型规范代表包括:EIA/TIA RS-232、EIA/TIA RS-449、V.35、RJ-45等。

    第二层:数据链路层(DataLinkLayer)
    在物理层提供比特流服务的基础上,建立相邻结点之间的数据链路,通过差错控制提供数据帧(Frame)在信道上无差错的传输,并进行各电路上的动作系列。数据链路层在不可靠的物理介质上提供可靠的传输。该层的作用包括:物理地址寻址、数据的成帧、流量控制、数据的检错、重发等。在这一层,数据的单位称为帧(frame)。数据链路层协议的代表包括:SDLC、HDLC、PPP、STP、帧中继等。

    第三层:网络层
    在 计算机网络中进行通信的两个计算机之间可能会经过很多个数据链路,也可能还要经过很多通信子网。网络层的任务就是选择合适的网间路由和交换结点, 确保数据及时传送。网络层将数据链路层提供的帧组成数据包,包中封装有网络层包头,其中含有逻辑地址信息- -源站点和目的站点地址的网络地址。如 果你在谈论一个IP地址,那么你是在处理第3层的问题,这是“数据包”问题,而不是第2层的“帧”。IP是第3层问题的一部分,此外还有一些路由协议和地 址解析协议(ARP)。有关路由的一切事情都在这第3层处理。地址解析和路由是3层的重要目的。网络层还可以实现拥塞控制、网际互连等功能。在这一层,数据的单位称为数据包(packet)。网络层协议的代表包括:IP、IPX、RIP、OSPF等。

    第四层:处理信息的传输层
    第4层的数据单元也称作数据包(packets)。但是,当你谈论TCP等具体的协议时又有特殊的叫法,TCP的数据单元称为段 (segments)而UDP协议的数据单元称为“数据报(datagrams)”。这个层负责获取全部信息,因此,它必须跟踪数据单元碎片、乱序到达的 数据包和其它在传输过程中可能发生的危险。第4层为上层提供端到端(最终用户到最终用户)的透明的、可靠的数据传输服务。所为透明的传输是指在通信过程中 传输层对上层屏蔽了通信传输系统的具体细节。传输层协议的代表包括:TCP、UDP、SPX等。

    第五层:会话层
    这一层也可以称为会晤层或对话层,在会话层及以上的高层次中,数据传送的单位不再另外命名,而是统称为报文。会话层不参与具体的传输,它提供包括访问验证和会话管理在内的建立和维护应用之间通信的机制。如服务器验证用户登录便是由会话层完成的。

    第六层:表示层
    这一层主要解决拥护信息的语法表示问题。它将欲交换的数据从适合于某一用户的抽象语法,转换为适合于OSI系统内部使用的传送语法。即提供格式化的表示和转换数据服务。数据的压缩和解压缩, 加密和解密等工作都由表示层负责。

    第七层:应用层

    应用层为操作系统或网络应用程序提供访问网络服务的接口。应用层协议的代表包括:Telnet、FTP、HTTP、SNMP等。

    除了层的数量之外,开放式系统互联(OSI)模型与TCP/IP协议有什么区别?

    开放式系统互联模型是一个参考标准,解释协议相互之间应该如何相互作用。TCP/IP协议是美国国防部发明的,是让互联网成为了目前这个样子的标准之一。开放式系统互联模型中没有清楚地描绘TCP/IP协议,但是在解释TCP/IP协议时很容易想到开放式系统互联模型。两者的主要区别如下:

    TCP/IP协议中的应用层处理开放式系统互联模型中的第五层、第六层和第七层的功能。

    TCP/IP协议中的传输层并不能总是保证在传输层可靠地传输数据包,而开放式系统互联模型可以做到。TCP/IP协议还提供一项名为UDP(用户数据报协议)的选择。UDP不能保证可靠的数据包传输。

    TCP/IP协议

    TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)协议属于传输层协议。其中TCP提供IP环境下的数据可靠传输,它提供的服务包括数据流传送、可靠性、有效流控、全双工操作和多路复用。通过面向连接、端到端和可靠的数据包发送。通俗说,它是事先为所发送的数据开辟出连接好的通道,然后再进行数据发送;而UDP则不为IP提供可靠性、流控或差错恢复功能。一般来说,TCP对应的是可靠性要求高的应用,而UDP对应的则是可靠性要求低、传输经济的应用。

    TCP支持的应用协议主要有:Telnet、FTP、SMTP等;UDP支持的应用层协议主要有:NFS(网络文件系统)、SNMP(简单网络管理协议)、DNS(主域名称系统)、TFTP(通用文件传输协议)等。

    TCP/IP协议与低层的数据链路层和物理层无关,这也是TCP/IP的重要特点。

    OSI是Open System Interconnect的缩写,意为开放式系统互联。

    展开全文
  • 今天,和一个朋友聊天,他是做.NET开发的(PS:鄙人是Java的虔诚信徒),朋友说他们最近在做的一个项目,使用了400个左右的存储过程代码封装了整个逻辑!光是存储过程代码在50000行左右!由此引发了我对软件开发的一些...
     今天,和一个朋友聊天,他是做.NET开发的(PS:鄙人是Java的虔诚信徒),朋友说他们最近在做的一个项目,使用了400个左右的存储过程代码封装 了整个逻辑!光是存储过程代码在50000行左右!由此引发了我对软件开发的一些思考,我在此只是抛砖引玉,欢迎大家参与讨论!
    

        现代软件开发为什么要分三层甚至n层,无非是为了实现“强内聚、松耦合”的目标,将软件“分而治之”,把问题划分开来各个解决,易于控制,易于实现组件重用,易于扩展和维护,易于分配资源。大大降低了开发和维护的成本,其优势不言而寓。

        以下是鄙人的一些愚见,有不对的地方望指正!

        1. 对于小项目,业务放存储过程里编程最简单,详细写好注释,避免数据从数据库到程序的来回传递,这也是存储过程的优点。
        2. 但是对于中大型项目,访问量很大,若将业务逻辑大量封装于存储过程中会导致数据库压力太大,中间层压力太小,资源闲置。这时就应该把业务放到中间层,中间层压力大的时候容易做服务器Cluster,做负载平衡。
        3. 做产品的时候,考虑到产品的移植性和安全性,应该尽量避免存储过程。
        4. 多人开发的时候,存储过程不是很方便做版本控制和管理。
        5. 关于Debug,调试不是很方便。
        6. 如果sp中有一大堆if, case, 然后每个begin  end之间有一大段处理, 甚至if case 还不断嵌套, 我认为这是在滥用sp, 这个应该是程序去完成的;   
          如果程序中从数据库中不断的拉数据和缓存数据,   结果就是为了几个表关联出点什么东西,   我认为是在滥用程序,   这个应该是sp去完成的

        7.存储过程的执行效率确实比较高,一些O/R mapping 框架如Hibernate则有性能瓶颈问题,用得不好会导致性能的下降,个人认为用ibatis 再在适当的时候结合使用存储过程,这样既可以兼顾OO的设计,又可以兼顾性能问题。
    展开全文
  • 应用在写好诊断服务后,需要调用网络接口对诊断服务的内容进行封装封装后的内容再进一步由数据链路进行封装,最后才能通过CAN将数据发送出去。 接收方: 接收到的CAN报文,将先后通过数据链路和网络对...

    在UDS诊断服务中:

    发送方:

    应用层在写好诊断服务后,需要调用网络层接口对诊断服务的内容进行封装,封装后的内容再进一步由数据链路层进行封装,最后才能通过CAN将数据发送出去。

    接收方:

    接收到的CAN报文,将先后通过数据链路层和网络层对报文的封装形式,来对报文进行解析,最终提取到和应用相关的信息。

    本文描述网络层对应用层诊断服务内容的封装形式。


    1、单帧诊断服务

    顾名思义,该类诊断服务通过一帧CAN报文即可完成诊断服务。

    在网络层中,也可以对此类报文称为SF(Single Frame)。

    SF第一字节的高4位为0,低4位为数据长度,其余字节为服务数据内容,没用到的数据可以按FF填充。诊断服务中,10  31等服务都属于SF,举例如下:

    10 03 请求进入诊断会话模式:

    02 10 03 FF FF FF FF FF

    31 01 启动例程控制服务(这里02 02是检查预编程条件)

    04 31 01 02 02 FF FF FF

    2、多帧传输服务

    当诊断数据长度超过7个字节时,就必须使用多帧传输,这里有如下三个概念:
    FF(First Frame)多帧传输的第一帧,其第一字节的高4位为1,低四位+第二字节为数据长度,其余字节为服务数据内容,没用到的数据可以按FF填充。

    CF(Consecutive Frame)多帧传输的连续帧,其第一字节最高四位为2,低4位为帧序号编号(0-F)。第一个连续帧的帧序号为1,之后的连续帧序号逐一增加,当序列号大于F时,重新从0开始。其余字节为服务数据内容,没用到的数据可以按FF填充。

    FC(Flow Control)多帧传输的流控制帧,其第一字节高四位为3,低四位为FS流控制状态

                                                              FS

                    数值描述
                     0表示允许发送方继续发送连续帧
    1表示发送方需等待下一条流控制帧[1],该流控制帧称为等待流控制帧
    2表示报文长度超出接收方的网络层缓存大小,此流控制帧将迫使发送方中断多帧报文的发送,并且发送方网络层使用N_USData.con向应用层报告N_Result = N_Buffer_Overflow。FS = Overflow的流控制帧接收方只能在接收到第一帧后发送。

    第二和第三字节分别为BS和ST

                                                                                BS

    数值描述
    0表示允许发送方连续发送连续帧,而不需要等待接收方发出的流控制帧
    1~255表示允许发送方连续发送连续帧的数目,发送完成相应数目的连续帧后,发送方必须等待接收方发出的流控制帧
                                                                                 ST

    数值描述
    0~127两个连续帧之间的最小间隔时间,0~127ms
    241~249100~900us
    250~255保留

    其余字节为服务数据内容,没用到的数据可以按FF填充。

    实际应用中,像一些数据传输功能都是多帧传输,举例如下:

    10 0B 34 00 44 00 00 20                               FF

    30 00 00 FF FF FF FF FF                             FC

    21 00 00 00 00 18 FF FF                              CF

    其中,FF定义的数据长度为00B为11,FC表示允许发送方继续发送连续帧,CF为连续帧的第一帧和最后一帧。


    以上也是对15765.2网络层本质的描述。





    展开全文
  • 我们知道现在的互联网中使用的TCP/IP协议是基于,OSI(开放系统互联)的七参考模型的,(虽然不是完全符合)从上到下分别为: 应用 表示 会话 传输 网络 数据链路和物理。 具体参看:...
  • 封装和信息隐藏

    千次阅读 2018-11-08 19:45:24
    文章目录3.1 信息隐藏原则3.1.1 封装与信息隐藏3.1.2 接口扮演的角色3.2 创建对象的基本模式3.2.1 门户大开型对象3.2.2 用命名规范区别私用成员3.2.3 作用域、嵌套函数和闭包3.2.4 用闭包实现私用成员3.3 更多高级...
  • 重新认识java(二) ---- 面向对象之封装

    千次阅读 多人点赞 2016-11-28 19:16:13
    如果你认为封装仅仅是private + getter and setter,那你就大错特错了!
  • 学习数据传输,是指在TCP/IP五结构模型的基础上来学习。 数据的封装过程:(以两台通信主机为例) 应用:原始数据被转换成二进制数据; 传输:二进制数据被分割成...网络:传输传来的数据被封装上IP头部 ...
  • 网络协议之数据的封装和解封装

    千次阅读 2020-02-28 21:12:17
    2、当数据包传送到传输时:传输会对数据包进一步封装、为该数据包添加TCP/UDP头,具体添加哪一个取决于应用使用得是哪一个,其中包含了源端口和目的端口号、源端口号往往是指定的、或是浏览器自动指派的端口。...
  • 不过本文并打算讨论和需求分析、获取商业逻辑相关的话题,而是针对系统设计方面进行探讨。 面临难题编码对于合格的PHP程序员来说并不是什么难事(也许只是花费时间长短的问题),因此系统分析和设计这一阶段
  • 计算机网络中的帧封装(C实现)

    千次阅读 2015-07-07 10:40:00
    这段时间开始复习计算机网络,看到帧封装这一节,结合以前的课程设计,就用C写了个帧封装的程序,说实话C学的确实怎么样,实现的时候对于文件操作那部分查了好多资料,下面说说帧封装是啥情况。  学过计算机网络...
  • 而这些库基本都提供了非常友好的接口,供应用使用。 但是,考虑到版本升级,程序员的水平,甚至使用许可等方面的问题,我考虑对未来大多数要使用的第三方库进行二次封装。 下面是我想到的基于多人开发,有可能...
  • 是针对点对点专线连接的接口的二层封装协议配置 PPP的PAP和CHAP验证,cpt支持,一定要在gns3上做实验。 路由器出厂默认是hdlc封装,修改为ppp封装后,可以采用pap验证或者chap验证。pap是明文传递,两次握手,chap...
  • TCP/IP 协议簇的逐层封装

    千次阅读 2015-02-02 21:01:31
    在使用 TCP 协议的网络程序中,... 1)链路通过加固定长度的首部、尾部来封装 IP 数据报(Datagram) 产生以太网帧(Frame),其中首部存在对封装数据的标识:是 IP(0x0800,本例) 、ARP(0x0806) 还是 RARP(0x0835)。
  • OSI七模型基础知识及各常见应用

    万次阅读 多人点赞 2018-03-16 15:37:33
    OSI七模型基础知识及各常见应用   目录 二、 OSI基础知识 三、 OSI的七结构 四、 OSI分层的优点 ...五、 OSI模型与TCP/IP模型的...每一负责一项具体的工作,然后把数据传送到下一。由低到高具体分为...
  • 使用第三方库是否应该做二次封装

    千次阅读 2016-10-08 14:47:27
    而这些库基本都提供了非常友好的接口,供应用使用。 但是,考虑到版本升级,程序员的水平,甚至使用许可等方面的问题,我考虑对未来大多数要使用的第三方库进行二次封装。 下面是我想到的基于多人开发,有可能...
  • 网络(1)

    千次阅读 2016-04-25 17:38:01
    网络的作用是:将分组从一台发送主机移动到一台接收主机。为此需要两种重要的网络功能: 转发:将分组从一个输入链路接口转移到适当的输出链路接口的路由器本地动作。 选路:分组从源到目的地时,决定端到端...
  • 讨论:在DAO中对Hibernate的封装

    千次阅读 2007-11-10 14:37:00
    我在现在的项目中在DAO中对Hiberante做了如下封装: 用一个HibernateDAO封装一些共同的操作: 代码 package 
  • 和上面两个高层封装不一样,使用TFLearn需要单独安装,安装的方法为: pip install tflearn 下面的代码介绍了如何通过TFLearn来实现卷积神经网络。更多关于TFLearn的用法介绍可以参考TFLearn的官方网站( ...
  • 音视频 RED 与 FEC 的 RTP 格式封装

    千次阅读 2019-03-13 16:16:30
    音视频冗余数据的RTP格式封装背景RTP格式 (RFC 3550)RED数据(RFC 2198)背景知识RED RTP 格式RED SDP 协商一般FEC的RTP载荷格式 (RFC 2733)基本原理Generic FEC 方案Generic FEC RTP 格式FEC 生成与恢复FEC 生成...
  • MVC模式、类封装还是黑客代码

    万次阅读 热门讨论 2004-09-07 11:30:00
    面临难题编码对于合格的PHP程序员来说并不是什么难事(也许只是花费...PHP的面向对象特性在现有版本中虽然得到了改善,但是还甚健全,根本足以担任面向对象设计的实现语言;即使眼光长远一些,在即将释出的以Zend E
  • 但是如果涉及到多参数参与变化的方法时怎么办呢? 可以使用map来进行封装再一起返回: main{ Map, Object> data =changeValue(count,content); System.out.println(data.get("count")); System.out.println(data....
  • 过去两个月深入的参与了一个分布式系统的开发,记得之前有人说过“想成为架构师之前,都是从微观架构开始的”。尽管我从没想过将来的某一天要成为一个架构师,或者领域专家,我只是想萌萌哒的编码,写着自己喜欢的...
  • 数据链路

    千次阅读 2013-06-29 19:42:40
    链路是协议栈的最低,它用于将网络的数据报通过路径中的单段链路节点到节点的传送,在路径上的不同链路可能具有不同的链路协议。...链路协议交换的数据单元称为帧,每个链路帧通常都封装一个网络报文。为
  • OSI七模型OSI是Open System Interconnect的缩写,意为开放式系统互联。OSI是Open System Interconnect的缩写,意为开放式系统互联。OSI七参考模型的各个层次的划分遵循下列原则: 同一中的各网络节点都有相同...
  • 理论上gateway则更适合于提高系统吞吐量(但一定能有更好的性能),最终性能还需要通过严密的压测来决定 (3):两者底层实现都是servlet,但是gateway多嵌套了一webflux框架 (4): zuul可用至其他微服务框架...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 65,266
精华内容 26,106
关键字:

不参与封装工作的层是