spring-jdbc 的實(shí)現(xiàn)原理

前言

本篇文章將回答以下幾個(gè)問題

  1. spring-jdbc 的出現(xiàn)是為了解決什么問題
  2. spring-jdbc 如何解決的這些問題
  3. 它的這種技術(shù)有何缺陷

首先希望你能帶著這些問題來(lái)看這篇文章,也希望這篇文章能讓你很好的解答這些問題悼院。當(dāng)然虑凛,這篇文章的終極目標(biāo)是希望你能夠借鑒spring-jdbc 的思想來(lái)解決我們?cè)诠ぷ鬟^程中所面臨的問題荒叶。
如果你想了解,如何使用spring-jdbc,請(qǐng)繞道......

Dao 模式

為了實(shí)現(xiàn)數(shù)據(jù)和業(yè)務(wù)的分離交胚,有人提出了Dao模式。Dao模式是數(shù)據(jù)處理的一種理想模式,(我認(rèn)為)它帶來(lái)了兩個(gè)方面的好處:1色难、屏蔽數(shù)據(jù)訪問的差異性;2等缀、業(yè)務(wù)與數(shù)據(jù)分離枷莉。spring-jdbc 在本質(zhì)上是一種Dao模式的具體實(shí)現(xiàn)。(Dao模式的詳細(xì)介紹
接下下我們用一個(gè)簡(jiǎn)單的例子(未具體實(shí)現(xiàn))來(lái)簡(jiǎn)單介紹一下Dao模式(如下圖所示)


從上面的UML圖可以知道:

  • 首先定義了一個(gè)User的操作接口UserDao,它定義了了獲取用戶信息尺迂、添加用戶信息笤妙、更改用戶信息的行為;
  • 具體行為的由其實(shí)現(xiàn)類來(lái)實(shí)現(xiàn)噪裕,我們這里舉了兩個(gè)例子:Batis 實(shí)現(xiàn)和Jdbc實(shí)現(xiàn)(當(dāng)然也可以緩存實(shí)現(xiàn)或file實(shí)現(xiàn)等)蹲盘,它實(shí)現(xiàn)具體獲取或修改數(shù)據(jù)的行為;UserDaoFactory 生成具體的實(shí)現(xiàn)UserDao實(shí)現(xiàn)類(請(qǐng)參考下面代碼)膳音。
  • 所以當(dāng)我們?cè)赟ervice層(UserService)訪問數(shù)據(jù)時(shí)召衔,只 需要使用UserDaoFactory 生成一個(gè)具體的UserDao實(shí)現(xiàn)類就可以了,這樣業(yè)務(wù)層就可以完全操作數(shù)據(jù)操作的具體實(shí)現(xiàn)( 參考下面UserService的具體實(shí)現(xiàn))
public class User {
    private int id;
    private String name;
    private String email;
    private String phone;
 
    public User() {
    }
 
    public int getId() {
        return id;
    }
 
    public String getName() {
        return name;
    }
 
    public String getEmail() {
        return email;
    }
 
    public String getPhone() {
        return phone;
    }
 
    public void setId(int id) {
        this.id = id;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public void setEmail(String email) {
        this.email = email;
    }
 
    public void setPhone(String phone) {
        this.phone = phone;
    }
}
public interface UserDaoInterface {
    public User getUserInfoByName(String name);
    public void putUserInfo(User user);
    public void updateUserInfo(User user);
}
public class UserDaoJdbcAccessImpl implements UserDaoInterface {
    // Jdbc連接數(shù)據(jù)庫(kù)等操作祭陷,未完成具體實(shí)現(xiàn)
    private DataSource dataSource;
    public User getUserInfoByName(String name) {
        dataSource.getC
 
        return new User();
    }
    public void putUserInfo(User user) {
 
    }
    public void updateUserInfo(User user) {
 
    }
}
public class UserDaoBatisAccessImpl implements UserDaoInterface {
    // Batis連接數(shù)據(jù)庫(kù)等操作苍凛,未完成具體實(shí)現(xiàn)
    public User getUserInfoByName(String name) {
        return new User();
    }
    public void putUserInfo(User user) {
 
    }
    public void updateUserInfo(User user) {
 
    }
}
public class UserDaoFacotry {
 
    public static UserDaoInterface getUserDao(int which) {
        switch(which) {
            case 1:
                return new UserDaoJdbcAccessImpl();
            case 2:
                return new UserDaoBatisAccessImpl();
            default:
                return null;
        }
    }
}
public class UserService {
 
    public UserDaoInterface getUserDaoOperation() {
        return UserDaoFacotry.getUserDao(1);
    }
 
    public void getUserInfo() {
 
        User user = this.getUserDaoOperation().getUserInfoByName("xiaoming");
    }
}

但在具體實(shí)現(xiàn)DaoImpl時(shí)遇到了一個(gè)問題,數(shù)據(jù)庫(kù)的連接訪問會(huì)拋出異常颗胡,且屬于checked exception

public User getUserInfoByName(String name) {
   try {
       Connection connection = dataSource.getConnection();
       User user = ....
       return user;
   } catch (SQLException e) {
        
   } finally {
       connection.close();
   }
}

這是很尷尬的毫深,因?yàn)榇藭r(shí)我們不知道是要拋給上層業(yè)務(wù)還是catch之后進(jìn)行處理。catch之后進(jìn)行處理毒姨,由于屏蔽異常會(huì)讓客戶端難以排查問題哑蔫,如果直接拋出去也帶來(lái)更嚴(yán)重的問題(必須更改接口且不同數(shù)據(jù)庫(kù)所拋出的異常不一樣),如下所示


public User getUserInfoByName(String name) throw SQLException, NamingException ... {
   try {
       Connection connection = dataSource.getConnection();
       User user = ....
       return user;
   } finally {
       connection.close();
   }
}

jdbc 為了解決不同數(shù)據(jù)庫(kù)帶來(lái)的異常差異化闸迷,則對(duì)異常進(jìn)行統(tǒng)一轉(zhuǎn)換嵌纲,并拋出unchecked異常。具體拋出的異承裙粒可以在org.springframework.dao中查看

這是很尷尬的逮走,因?yàn)榇藭r(shí)我們不知道是要拋給上層業(yè)務(wù)還是catch之后進(jìn)行處理。catch之后進(jìn)行處理今阳,由于屏蔽異常會(huì)讓客戶端難以排查問題师溅,如果直接拋出去也帶來(lái)更嚴(yán)重的問題(必須更改接口且不同數(shù)據(jù)庫(kù)所拋出的異常不一樣),如下所示

具體異常所代表的含義:
Spring的DAO異常層次

| 異常 | 何時(shí)拋出 |
| :-------- :|: --------:|
| CleanupFailureDataAccessException |一項(xiàng)操作成功地執(zhí)行盾舌,但在釋放數(shù)據(jù)庫(kù)資源時(shí)發(fā)生異常(例如墓臭,關(guān)閉一個(gè)Connection |
| DataAccessResourceFailureException |數(shù)據(jù)訪問資源徹底失敗,例如不能連接數(shù)據(jù)庫(kù) |
| iMac | 10000 元 |
|DataIntegrityViolationException| Insert或Update數(shù)據(jù)時(shí)違反了完整性妖谴,例如違反了惟一性限制|
|DataRetrievalFailureException |某些數(shù)據(jù)不能被檢測(cè)到窿锉,例如不能通過關(guān)鍵字找到一條記錄|
|DeadlockLoserDataAccessException| 當(dāng)前的操作因?yàn)樗梨i而失敗|
|IncorrectUpdateSemanticsDataAccessException| Update時(shí)發(fā)生某些沒有預(yù)料到的情況,例如更改超過預(yù)期的記錄數(shù)膝舅。當(dāng)這個(gè)異常被拋出時(shí)嗡载,執(zhí)行著的事務(wù)不會(huì)被回滾|
|InvalidDataAccessApiusageException 一個(gè)數(shù)據(jù)訪問的JAVA| API沒有正確使用,例如必須在執(zhí)行前編譯好的查詢編譯失敗了|
|invalidDataAccessResourceUsageException |錯(cuò)誤使用數(shù)據(jù)訪問資源仍稀,例如用錯(cuò)誤的SQL語(yǔ)法訪問關(guān)系型數(shù)據(jù)庫(kù)|
|OptimisticLockingFailureException |樂觀鎖的失敗洼滚。這將由ORM工具或用戶的DAO實(shí)現(xiàn)拋出|
|TypemismatchDataAccessException| Java類型和數(shù)據(jù)類型不匹配,例如試圖把String類型插入到數(shù)據(jù)庫(kù)的數(shù)值型字段中|
|UncategorizedDataAccessException| 有錯(cuò)誤發(fā)生琳轿,但無(wú)法歸類到某一更為具體的異常中|

spring-jdbc

我們可以將spring-jdbc 看作Dao 模式的一個(gè)最佳實(shí)踐判沟,它只是使用了template模式,實(shí)現(xiàn)了最大化的封裝崭篡,以減少用戶使用的復(fù)雜性。spring-jdbc 提供了兩種模式的封裝吧秕,一種是Template,一種是操作對(duì)象的模式琉闪。操作對(duì)象的模式只是提供了面向?qū)ο蟮囊曈X(template 更像面向過程),其底層的實(shí)現(xiàn)仍然是采用Template砸彬。
接下來(lái)我們將會(huì)了解Template 的封裝過程颠毙。

2.1 Template

還是延用上述例子,如果這里我們需要根據(jù)用戶名查詢用戶的完整信息砂碉,將采用下面的方式實(shí)現(xiàn)查詢

public class UserDaoJdbcAccessImpl implements UserDaoInterface {
    // Jdbc連接數(shù)據(jù)庫(kù)等操作蛀蜜,未完成具體實(shí)現(xiàn)
    private DataSource dataSource;
    public User getUserInfoByName(String name) {
        String sql = "....." + name;
        Connection connection = null;
        try {
            connection = DataSourceUtils.getConnection(dataSource);
            Statement statement = connection.createStatement();
            ResultSet resultSet = statement.executeQuery(sql);
            List<User> userList = Lists.newArrayList();
            while(resultSet.next()) {
                User user = new User();
                user.setId(resultSet.getInt(1));
                user.setName(name);
                user.setEmail(resultSet.getString(3));
                user.setPhone(resultSet.getString(4));
                userList.add(user);
            }
            connection.close();
            connection = null;
            statement.close();
            return userList;
        } catch (Exception e) {
            throw new DaoException(e);
        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    log.error(".....");
                }
            }
        }
    }

當(dāng)我們只需要完成一個(gè)操作的項(xiàng)目時(shí),這種方式還可以接受增蹭,但當(dāng)項(xiàng)目中有大量的DAO需要操作時(shí)滴某,難免過程中會(huì)出現(xiàn)各種問題,如忘記關(guān)閉連接等。
其實(shí)我們可以發(fā)現(xiàn)整個(gè)的數(shù)據(jù)庫(kù)的操作實(shí)現(xiàn)可以分為四個(gè)部分:資源管理(數(shù)據(jù)庫(kù)的連接關(guān)閉等操作)霎奢、sql執(zhí)行(查詢户誓、更新等)、結(jié)果集的處理(將sql查詢結(jié)果轉(zhuǎn)化)幕侠、異常處理帝美。
那是不是可以將公共部分抽象成一個(gè)模板進(jìn)行使用呢?現(xiàn)在我們來(lái)定義一個(gè)Jdbc的一個(gè)模板

public class JdbcTemplate {
    public final Object execute(StatementCallback callback) {
        Connection connection = null;
        Statement statement = null;
        try {
            connection = getConnetion();
            statement = con.createStatement();
            Object ret = callback.doWithStatement(callback);
            return retValue;
        } catch (SQLException e) {
            DateAccessException ex = translateSqlException(e);
            throw ex;
        } finally {
            closeStatement(statement);
            releaseConnection(connection);
        }
    }
}

Template 定義了關(guān)注了操作的所有過程晤硕,只需要傳遞一個(gè)callback,就可以幫我們處理各種細(xì)節(jié)化操作悼潭,這些細(xì)節(jié)化操作包括:獲取數(shù)據(jù)庫(kù)連接;執(zhí)行操作舞箍;處理異常舰褪;資源釋放。那我們?cè)谑褂脮r(shí)就可以簡(jiǎn)化為

private JdbcTemplate jdbcTemplate;
// Jdbc連接數(shù)據(jù)庫(kù)等操作创译,未完成具體實(shí)現(xiàn)
private DataSource dataSource;
public User getUserInfoByName(String name) {
    StatementCallback statementCallback = new StatementCallback() {
        @Override
        public Object doInStatement(Statement stmt) throws SQLException, DataAccessException {
            return null;
        }
    }
     return jdbcTemplate.execute(statementCallback);
     
}

實(shí)際上抵知,Template 在封裝時(shí)遠(yuǎn)比這個(gè)復(fù)雜,接下來(lái)我們就看一下spring-jdbc 是如何對(duì)jdbc進(jìn)行封裝的

JdbcTemplate 實(shí)現(xiàn)了JdbcOperations接口和繼承了JdbcAccessor软族。
JdbcOperations 定義了數(shù)據(jù)庫(kù)的操作,excute刷喜、 query、update 等立砸,它是對(duì)行為的一種封裝掖疮。
JdbcAccessor 封裝了對(duì)資源的操作以及異常的處理,可以看一下源碼颗祝,比較短浊闪。

public abstract class JdbcAccessor implements InitializingBean {
 
   /** Logger available to subclasses */
   protected final Log logger = LogFactory.getLog(getClass());
 
   private DataSource dataSource;
 
   private SQLExceptionTranslator exceptionTranslator;
 
   private boolean lazyInit = true;
 
 
   /**
    * Set the JDBC DataSource to obtain connections from.
    */
   public void setDataSource(DataSource dataSource) {
      this.dataSource = dataSource;
   }
 
   /**
    * Return the DataSource used by this template.
    */
   public DataSource getDataSource() {
      return this.dataSource;
   }
 
   /**
    * Specify the database product name for the DataSource that this accessor uses.
    * This allows to initialize a SQLErrorCodeSQLExceptionTranslator without
    * obtaining a Connection from the DataSource to get the metadata.
    * @param dbName the database product name that identifies the error codes entry
    * @see SQLErrorCodeSQLExceptionTranslator#setDatabaseProductName
    * @see java.sql.DatabaseMetaData#getDatabaseProductName()
    */
   public void setDatabaseProductName(String dbName) {
      this.exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dbName);
   }
 
   /**
    * Set the exception translator for this instance.
    * <p>If no custom translator is provided, a default
    * {@link SQLErrorCodeSQLExceptionTranslator} is used
    * which examines the SQLException's vendor-specific error code.
    * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
    * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
    */
   public void setExceptionTranslator(SQLExceptionTranslator exceptionTranslator) {
      this.exceptionTranslator = exceptionTranslator;
   }
 
   /**
    * Return the exception translator for this instance.
    * <p>Creates a default {@link SQLErrorCodeSQLExceptionTranslator}
    * for the specified DataSource if none set, or a
    * {@link SQLStateSQLExceptionTranslator} in case of no DataSource.
    * @see #getDataSource()
    */
   public synchronized SQLExceptionTranslator getExceptionTranslator() {
      if (this.exceptionTranslator == null) {
         DataSource dataSource = getDataSource();
         if (dataSource != null) {
            this.exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource);
         }
         else {
            this.exceptionTranslator = new SQLStateSQLExceptionTranslator();
         }
      }
      return this.exceptionTranslator;
   }
 
   /**
    * Set whether to lazily initialize the SQLExceptionTranslator for this accessor,
    * on first encounter of a SQLException. Default is "true"; can be switched to
    * "false" for initialization on startup.
    * <p>Early initialization just applies if {@code afterPropertiesSet()} is called.
    * @see #getExceptionTranslator()
    * @see #afterPropertiesSet()
    */
   public void setLazyInit(boolean lazyInit) {
      this.lazyInit = lazyInit;
   }
 
   /**
    * Return whether to lazily initialize the SQLExceptionTranslator for this accessor.
    * @see #getExceptionTranslator()
    */
   public boolean isLazyInit() {
      return this.lazyInit;
   }
 
   /**
    * Eagerly initialize the exception translator, if demanded,
    * creating a default one for the specified DataSource if none set.
    */
   @Override
   public void afterPropertiesSet() {
      if (getDataSource() == null) {
         throw new IllegalArgumentException("Property 'dataSource' is required");
      }
      if (!isLazyInit()) {
         getExceptionTranslator();
      }
   }
 
}

源碼有三個(gè)參數(shù):datasource、exceptionTranslator(轉(zhuǎn)換各種數(shù)據(jù)庫(kù)方案商的不同的數(shù)據(jù)庫(kù)異常)螺戳、lazyInit(延時(shí)加載:是否在applicationContext 初始化時(shí)就進(jìn)行實(shí)例化)

在使用的過程中我們可以看到搁宾,只需要提供一個(gè)statementCallback,就可以實(shí)現(xiàn)對(duì)Dao 的各種操作。spring-jdbc 為了滿足各種場(chǎng)景的需要倔幼,為我們提供了四組不同權(quán)限的callback

在使用的過程中我們可以看到盖腿,只需要提供一個(gè)statementCallback,就可以實(shí)現(xiàn)對(duì)Dao 的各種操作。spring-jdbc 為了滿足各種場(chǎng)景的需要损同,為我們提供了四組不同權(quán)限的callback

| callback | 說明 |
| :-------- :|: --------:|
|CallableStatementCallback|面向存儲(chǔ)過程|
|ConnectionCallback|面向連接的call,權(quán)限最大(但一般情況應(yīng)該避免使用翩腐,造成操作不當(dāng))|
|PreparedStatementCallback|包含查詢?cè)儏?shù)的的callback,可以防止sql 注入|
|StatementCallback|縮小了ConnectionCallback的權(quán)限范圍,不允許操作數(shù)據(jù)庫(kù)的連接|

我們?cè)倏匆幌翵dbcTemplate 的封裝

public <T> T execute(ConnectionCallback<T> action) throws DataAccessException {
   Assert.notNull(action, "Callback object must not be null");
 
   Connection con = DataSourceUtils.getConnection(getDataSource());
   try {
      Connection conToUse = con;
      if (this.nativeJdbcExtractor != null) {
         // Extract native JDBC Connection, castable to OracleConnection or the like.
         conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
      }
      else {
         // Create close-suppressing Connection proxy, also preparing returned Statements.
         conToUse = createConnectionProxy(con);
      }
      return action.doInConnection(conToUse);
   }
   catch (SQLException ex) {
      // Release Connection early, to avoid potential connection pool deadlock
      // in the case when the exception translator hasn't been initialized yet.
      DataSourceUtils.releaseConnection(con, getDataSource());
      con = null;
      throw getExceptionTranslator().translate("ConnectionCallback", getSql(action), ex);
   }
   finally {
      DataSourceUtils.releaseConnection(con, getDataSource());
   }
}

有兩個(gè)需要注意的地方

Connection con = DataSourceUtils.getConnection(getDataSource());

這里創(chuàng)建連接使用的是DataSourceUtils,而不是datasource.getConnection,這是由于考慮到了事務(wù)處理的因素膏燃。

if (this.nativeJdbcExtractor != null) {
         // Extract native JDBC Connection, castable to OracleConnection or the like.
         conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
      }

這里并不一定使用的是jdbc的connection,因?yàn)閖dbc是一種統(tǒng)一化封裝茂卦,而忽略了各個(gè)sql供應(yīng)商的差異性。有時(shí)間我們需要使用某一數(shù)據(jù)庫(kù)的某種特性(比如Oracle sql)時(shí)组哩,就可以通過對(duì)nativeJdbcExtractor來(lái)達(dá)到目的等龙。
JdbcTemplate 還有幾個(gè)演生的template,這里都不再詳細(xì)介紹处渣。
Ok,關(guān)于template 的介紹就到此為止(這里更傾向于介紹各種技術(shù)的實(shí)現(xiàn)原理,而非如何使用)而咆。

2.2 對(duì)象模式

對(duì)象模式其實(shí)只是把Template 中的操作封裝成各個(gè)對(duì)象霍比,而其本質(zhì)的實(shí)現(xiàn)方式仍然是Template

三、缺陷

spring-jdbc的封裝方式得到了廣泛認(rèn)可暴备,但并不代表它是一個(gè)友好的的操作數(shù)據(jù)庫(kù)的工具悠瞬。 從上面的介紹過程中,我們可以感受到j(luò)dbc 的封裝是面向底層的涯捻,所以它對(duì)于上層的使用方并不那么友好浅妆。jdbc 并未能真正的實(shí)現(xiàn)業(yè)務(wù)和數(shù)據(jù)的完全分離,對(duì)callback的定義仍然會(huì)穿插在業(yè)務(wù)當(dāng)中障癌,所以在實(shí)際的業(yè)務(wù)應(yīng)用中凌外,已經(jīng)很少直接使用jdbc。因此spring 也對(duì)很多其它的ORM框架進(jìn)行了支持涛浙,如ibatis,hibernate,JDO等等康辑,這些更高級(jí)對(duì)用戶更加友好。接下我會(huì)用一系列文章轿亮,對(duì)這些框架進(jìn)行介紹

四疮薇、總結(jié)

我們?cè)賮?lái)回顧一下最前面提出的三個(gè)問題:

  1. spring-jdbc 是為了解決數(shù)據(jù)和業(yè)務(wù)分離的問題,使客戶端能夠更專注于業(yè)務(wù)層面我注,而不必關(guān)注數(shù)據(jù)庫(kù)資源的連接釋放及異常處理等邏輯按咒。
  2. spring-jdbc 采用dao模式實(shí)現(xiàn)了業(yè)務(wù)和數(shù)據(jù)的分離;使用模板模式但骨,實(shí)現(xiàn)了邏輯的封裝
  3. spring-jdbc 屬于面向低層的實(shí)現(xiàn)励七,對(duì)用戶不太友好。

個(gè)人能力有限奔缠,有錯(cuò)誤之處還請(qǐng)指證.....

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末掠抬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子校哎,更是在濱河造成了極大的恐慌剿另,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,640評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贬蛙,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡谚攒,警方通過查閱死者的電腦和手機(jī)阳准,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)馏臭,“玉大人野蝇,你說我怎么就攤上這事讼稚。” “怎么了绕沈?”我有些...
    開封第一講書人閱讀 165,011評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵锐想,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我乍狐,道長(zhǎng)赠摇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,755評(píng)論 1 294
  • 正文 為了忘掉前任浅蚪,我火速辦了婚禮藕帜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘惜傲。我一直安慰自己洽故,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評(píng)論 6 392
  • 文/花漫 我一把揭開白布盗誊。 她就那樣靜靜地躺著时甚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪哈踱。 梳的紋絲不亂的頭發(fā)上荒适,一...
    開封第一講書人閱讀 51,610評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音嚣鄙,去河邊找鬼吻贿。 笑死,一個(gè)胖子當(dāng)著我的面吹牛哑子,可吹牛的內(nèi)容都是我干的舅列。 我是一名探鬼主播,決...
    沈念sama閱讀 40,352評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼卧蜓,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼帐要!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起弥奸,我...
    開封第一講書人閱讀 39,257評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤榨惠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后盛霎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赠橙,經(jīng)...
    沈念sama閱讀 45,717評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評(píng)論 3 336
  • 正文 我和宋清朗相戀三年愤炸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了期揪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,021評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡规个,死狀恐怖凤薛,靈堂內(nèi)的尸體忽然破棺而出姓建,到底是詐尸還是另有隱情,我是刑警寧澤缤苫,帶...
    沈念sama閱讀 35,735評(píng)論 5 346
  • 正文 年R本政府宣布速兔,位于F島的核電站,受9級(jí)特大地震影響活玲,放射性物質(zhì)發(fā)生泄漏涣狗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評(píng)論 3 330
  • 文/蒙蒙 一翼虫、第九天 我趴在偏房一處隱蔽的房頂上張望屑柔。 院中可真熱鬧,春花似錦珍剑、人聲如沸掸宛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)唧瘾。三九已至,卻和暖如春别凤,著一層夾襖步出監(jiān)牢的瞬間饰序,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工规哪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留求豫,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,224評(píng)論 3 371
  • 正文 我出身青樓诉稍,卻偏偏與公主長(zhǎng)得像蝠嘉,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子杯巨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理蚤告,服務(wù)發(fā)現(xiàn),斷路器服爷,智...
    卡卡羅2017閱讀 134,657評(píng)論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,822評(píng)論 6 342
  • 01 這個(gè)話題好像已經(jīng)被說的爛大街了杜恰,同樣我也知道我的題目特別特別俗。但只有這兩個(gè)詞才能形容我接下來(lái)說的事了仍源。 0...
    都樂很可愛閱讀 197評(píng)論 0 1
  • 球球的游戲規(guī)則就是大球吃小球心褐,最終的目的就是生存下來(lái)。剛開始我很不解笼踩,就一直吃那些小點(diǎn)點(diǎn)有什么好玩的檬寂,可當(dāng)我真的試...
    方默默閱讀 546評(píng)論 6 5
  • 文/若杉 假日,閑來(lái)翻看朋友圈价涝,見友人發(fā)了這樣一條消息: “最無(wú)聊的7天開始了女蜈,不知道去哪兒,也沒人約色瘩,繼續(xù)在家睡...
    若杉閱讀 969評(píng)論 11 18