Mybatis隨筆(十) 聊聊NameSpace

在我剛接觸Mybatis那會(huì),有位先生說(shuō)到這個(gè)NameSpace時(shí)薯鼠,說(shuō)這個(gè)東西不一定就要寫對(duì)應(yīng)Mapper接口的全限定類名呀潭,我就來(lái)試了試。

原來(lái)當(dāng)年先生少說(shuō)了一句降传,或是我走神漏聽了一句篷朵,才有了現(xiàn)在的Mybatis系列。


首先我的目錄結(jié)構(gòu)


package
  • 注意 Mapper.java 與 Mapper.xml 文件不在同一目錄下就行了

先看幾種用法及其效果

1. NameSpace 使用全限定類名 & mapper 配置 resource
  • 配置文件 mybatis-config.xml
<mapper resource="resources/mapper/AccountMapper.xml" />
  • sql映射文件 AccountMaper.xml
<mapper namespace="com.hy.test.dao.AccountMapper" >
  • 使用
@Test
public void queryAccountTest() {

    basicMethod("queryAccountTest", k -> {
        AccountMapper accountMapper = k.getMapper(AccountMapper.class);
        Account account = accountMapper.selectByPrimaryKey(31);
        log.info("---- account: " + account.toString() + " -----------");
        return SUCCESS;
    });
}

/**
 * SqlSessionFactory -> SqlSession
 */
private void basicMethod(String methodName, Function<SqlSession, String> mappingFunction) {
    String config = "resources/mybatis-config.xml";
    // 測(cè)試代碼
    SqlSession sqlSession = null;
    try (InputStream is = Resources.getResourceAsStream(config)) {
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        sqlSession = factory.openSession();
        log.error("function: " + methodName + ", result: " + mappingFunction.apply(sqlSession));
        sqlSession.commit();
    } catch (Exception ignore) {
        log.error(ignore);
        ignore.printStackTrace();
    } finally {
        if (null != sqlSession ) sqlSession.close();
    }
}

輸出

==>  Preparing: select id, name, balance from account where id = ? 
==> Parameters: 31(Integer)
Account: class com.hy.test.po.Account
object test: com.hy.test.po.Account@6a1aab78
<==      Total: 1
---- account: Account{id=31, name='java', balance=2} -----------

這種方式無(wú)疑是可以正確讀取的

2. NameSpace 不使用全限定類名 & mapper 配置 resource

修改一下NameSpace婆排,其他不變
sql映射文件 AccountMaper.xml

<mapper namespace="lalalala" >

執(zhí)行結(jié)果

org.apache.ibatis.binding.BindingException: Type interface com.hy.test.dao.AccountMapper is not known to the MapperRegistry.
    at org.apache.ibatis.binding.MapperRegistry.getMapper(MapperRegistry.java:47)

    at org.apache.ibatis.session.Configuration.getMapper(Configuration.java:779)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.getMapper(DefaultSqlSession.java:291)
    at com.hy.test.main.MainTest.lambda$queryAccountTest$1(MainTest.java:52)
    at com.hy.test.main.MainTest.basicMethod(MainTest.java:93)
    at com.hy.test.main.MainTest.queryAccountTest(MainTest.java:51)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

報(bào)錯(cuò) MapperRegistry 沒找到接口声旺,前面的源碼解析我們知道這個(gè) MapperRegistry 是 Configuration 的一個(gè)屬性控硼,其中有一個(gè) Map類型的knownMappers 用于存儲(chǔ)加載過(guò)的 *Mapper.java 接口類型與對(duì)應(yīng)的JDK動(dòng)態(tài)代理工廠的映射。

那我們是不是可以手動(dòng)將 Mapper接口注入到這個(gè) MapperRegistry 呢艾少?

3. NameSpace 不使用全限定類名 & mapper 配置 resource/class

配置文件 mybatis-config.xml

<mapper resource="resources/mapper/AccountMapper.xml" />

sql映射文件 AccountMaper.xml

<mapper namespace="com.hy.test.dao.AccountMapper" >
<mapper class="com.hy.test.dao.AccountMapper"/>
  • 添加了 class 屬性就可以指定加載 AccountMapper 接口類了

執(zhí)行結(jié)果

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.hy.test.dao.AccountMapper.selectByPrimaryKey
    at org.apache.ibatis.binding.MapperMethod.<init>(MapperMethod.java:53)

    at org.apache.ibatis.binding.MapperProxy.lambda$cachedInvoker$0(MapperProxy.java:107)
    at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660)
    at org.apache.ibatis.binding.MapperProxy.cachedInvoker(MapperProxy.java:94)
    at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:85)
    at com.sun.proxy.$Proxy6.selectByPrimaryKey(Unknown Source)
    at com.hy.test.main.MainTest.lambda$queryAccountTest$1(MainTest.java:53)
    at com.hy.test.main.MainTest.basicMethod(MainTest.java:93)
    at com.hy.test.main.MainTest.queryAccountTest(MainTest.java:51)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

這個(gè)報(bào)錯(cuò)是告訴我們,你想要執(zhí)行的SQL方法在 Configuration 的mappedStatements 中沒有對(duì)應(yīng)的 MapperStatement#id

Configuration.java 類源碼

protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());

public void addMappedStatement(MappedStatement ms) {
    mappedStatements.put(ms.getId(), ms);
}

其中 id 可以查詢調(diào)用軌跡

public String applyCurrentNamespace(String base, boolean isReference) {
    if (base == null) {
        return null;
    }
    if (isReference) {
        // is it qualified with any namespace yet?
        if (base.contains(".")) {
            return base;
        }
    } else {
        // is it qualified with this namespace yet?
        if (base.startsWith(currentNamespace + ".")) {
            return base;
        }
        if (base.contains(".")) {
            throw new BuilderException("Dots are not allowed in element names, please remove it from " + base);
        }
    }
    return currentNamespace + "." + base;
}
  • id 的值是 namespace + "." + 方法名

這就很明顯了

  • 配置接口類加載進(jìn)去只是為了獲取到對(duì)應(yīng)的動(dòng)態(tài)代理對(duì)象翼悴,可是執(zhí)行的時(shí)候是從 mappedStatements 中取對(duì)應(yīng)的 MapperStatement 執(zhí)行的
  • 而 mappedStatements 中的id是 lalalala.selectByPrimaryKey缚够,而你代理對(duì)象用的是com.hy.test.dao.AccountMapper.selectByPrimaryKey 自然取不到
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
    final String methodName = method.getName();
    final Class<?> declaringClass = method.getDeclaringClass();
    MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
    if (ms == null) {
        if (method.getAnnotation(Flush.class) != null) {
            name = null;
            type = SqlCommandType.FLUSH;
        } else {
            // here
            // here
            // here
            throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName);
        }
    } else {
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
            throw new BindingException("Unknown execution method for: " + name);
        }
    }
}

private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass, Configuration configuration) {
    // 接口名 + 方法名 作為 statementId
    String statementId = mapperInterface.getName() + "." + methodName;
    if (configuration.hasStatement(statementId)) {
        return configuration.getMappedStatement(statementId);
    } else if (mapperInterface.equals(declaringClass)) {
        return null;
    }
    // 允許繼承
    for (Class<?> superInterface : mapperInterface.getInterfaces()) {
        if (declaringClass.isAssignableFrom(superInterface)) {
            MappedStatement ms = resolveMappedStatement(superInterface, methodName,
            declaringClass, configuration);
            if (ms != null) {
                return ms;
            }
        }
    }
    return null;
}

歸根究底,就是 MapperStatement 的 id 不一致鹦赎,也就是 NameSpace 沒有寫接口類的全限定類名谍椅。

那么 NameSpace 一定要寫接口類的全限定類名嗎?

4. 換種思路

Configuration類在解析時(shí)有兩個(gè)屬性很重要

  1. mapperRegistry
  • 判斷是否加載過(guò) Mapper.java
  • 內(nèi)有屬性 Configuration configuration
  • Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
  • if (configuration.hasMapper(Class<T> type)) {}
  1. loadedResources
  • 通過(guò) XML 的命名空間判斷是否解析過(guò) Mapper.xml
  • Set<String> loadedResources = new HashSet<>();
  • 命名空間是否有效取決于是否能被類加載器加載
  • if (configuration.isResourceLoade(String resource)) {}

由于mappedStatements存儲(chǔ)的是具體的SQL實(shí)現(xiàn)古话,那么mappedStatements屬性的構(gòu)建可以有兩個(gè)來(lái)源雏吭,一個(gè)就是通過(guò)配置文件的形式,也就是 XML陪踩;一個(gè)就是通過(guò) Mapper.java 接口方法上的注解杖们,比如

@SelectKey(keyProperty = "account",
        before = false,
        statementType = StatementType.STATEMENT,
        statement = "select * from account where id = #{id}",
        resultType = Account.class)
Account selectByPrimaryKey(@Param("id") Integer id);

既然我們使用的是 XML 方式,那么mappedStatements中的key就只能是 namespace + 方法ID肩狂,而代理對(duì)象用于執(zhí)行的SqlId只能是 mapperInterface.getName + 方法ID摘完,這么來(lái)看好像我們只能把 XML 的 NameSpace 寫成接口類的全限定類名了。

不妨換種思路傻谁。

既然配置文件我們已經(jīng)沒辦法再修改孝治,試試修改執(zhí)行方式呢

@Test
public void Three() {
    try {
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("resources/mybatis-config.xml"));
        SqlSession session = sqlSessionFactory.openSession();
        Account a = session.selectOne("selectByPrimaryKey", 31);
        System.out.println(a.toString());
    } catch (IOException e) {
        e.printStackTrace();
    }
}

執(zhí)行結(jié)果

==>  Preparing: select id, name, balance from account where id = ? 
==> Parameters: 31(Integer)
Account: class com.hy.test.po.Account
object test: Account{id=null, name='null', balance=null}
<==      Total: 1
Account{id=31, name='java', balance=2}

ok,這種方式是可以成功的审磁。

來(lái)分析下這種方式為什么可以
直接找到對(duì)應(yīng)SqlSession的具體執(zhí)行

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        MappedStatement ms = configuration.getMappedStatement(statement);
        return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

這里也是直接從configuration的mapperStatements中取MapperStatement的谈飒,不過(guò)id就換成了selectByPrimaryKey,為什么這里可以直接取到值呢态蒂?

很簡(jiǎn)單杭措,往mapperStatements中塞值的時(shí)候,不僅會(huì)塞一個(gè)長(zhǎng)id吃媒,還會(huì)再加一個(gè)短id瓤介,我們?cè)倏聪耺apperStatements的實(shí)現(xiàn)

protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());

注意到這不是一個(gè)常見的 HashMap,而是一個(gè) StrictMap
其重寫了put方法

protected static class StrictMap<V> extends HashMap<String, V> {

    @Override
    @SuppressWarnings("unchecked")
    public V put(String key, V value) {
        if (containsKey(key)) {
            throw new IllegalArgumentException(name + " already contains value for " + key + (conflictMessageProducer == null ? "" : conflictMessageProducer.apply(super.get(key), value)));
        }
        if (key.contains(".")) {
            final String shortKey = getShortName(key);
            if (super.get(shortKey) == null) {
                super.put(shortKey, value);
            } else {
                super.put(shortKey, (V) new Ambiguity(shortKey));
            }
        }
        return super.put(key, value);
    }

    private String getShortName(String key) {
        final String[] keyParts = key.split("\\.");
        return keyParts[keyParts.length - 1];
    }
}
  • 所以debug可以看到mapperStatements的長(zhǎng)度是接口方法的兩倍赘那,就是因?yàn)榧恿?shortName
mapperStatements
AccountMapper.java
  • 6個(gè)接口方法刑桑,12的mapperStatements長(zhǎng)度
  • 再仔細(xì)看下 mapperStatements 里面的值都是一長(zhǎng)key帶一短key

所以通過(guò) SqlSession 直接調(diào)用的方式是可以忽略 NameSpace 的校驗(yàn)的,只要唯一就行了募舟,不然多個(gè) xml 使用同一個(gè) NameSpace 則只會(huì)加載一個(gè)祠斧。

總結(jié)

  1. NameSpace的值并不一定是要配置成接口的全限定類名,前提是不使用類型安全的代理映射方式來(lái)執(zhí)行SQL拱礁,而是直接使用 SqlSession 來(lái)直接使用方法ID調(diào)用指定方法琢锋,這種方式就不怎么優(yōu)雅
  2. 配置class- Mapper.java或package - name 指定加載接口類辕漂,解析時(shí)會(huì)去嘗試加載同目錄下同名的xml文件(支持 classPath)
  3. 配置resource - Mapper.xml 指定加載 XML 文件,解析時(shí)會(huì)嘗試將其中 NameSpace 當(dāng)做接口Mapper進(jìn)行加載吴超,前提是NameSpace 可以被類加載器加載
private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
        Class<?> boundType = null;
        try {
            boundType = Resources.classForName(namespace);
        } catch (ClassNotFoundException e) {
            //ignore, bound type is not required
        }
        if (boundType != null) {
            // 先判斷是否已經(jīng)加載
            if (!configuration.hasMapper(boundType)) {
                // Spring may not know the real resource name so we set a flag
                // to prevent loading again this resource from the mapper interface
                // look at MapperAnnotationBuilder#loadXmlResource
                configuration.addLoadedResource("namespace:" + namespace);
                configuration.addMapper(boundType);
            }
        }
    }
}
  • 先判斷是否已經(jīng)加載钉嘹,若是已經(jīng)記載再直接加載會(huì)報(bào)錯(cuò)
if (hasMapper(type)) {
    throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}

看個(gè)配置實(shí)例,在NameSpace寫了全限定類名時(shí)鲸阻,下面三種配置方式

<mapper resource="resources/mapper/AccountMapper.xml" />
<mapper class="com.hy.test.dao.AccountMapper"/>
<mapper resource="resources/mapper/AccountMapper.xml" />
<mapper resource="resources/mapper/AccountMapper.xml" />
<mapper class="com.hy.test.dao.AccountMapper"/>

1與2 都能成功且效果是一樣的跋涣,而3 會(huì)報(bào)錯(cuò)

### Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. 
Cause: org.apache.ibatis.binding.BindingException: 

Type interface com.hy.test.dao.AccountMapper is already known to the MapperRegistry.

注意我的代碼目錄,Mapper.java 與 Mapper.xml 不在同一級(jí)目錄下


謝謝當(dāng)年先生沒說(shuō)完的后面半句話鸟悴。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末陈辱,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子细诸,更是在濱河造成了極大的恐慌沛贪,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件震贵,死亡現(xiàn)場(chǎng)離奇詭異利赋,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)猩系,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門隐砸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蝙眶,你說(shuō)我怎么就攤上這事季希。” “怎么了幽纷?”我有些...
    開封第一講書人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵式塌,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我友浸,道長(zhǎng)峰尝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任收恢,我火速辦了婚禮武学,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘伦意。我一直安慰自己火窒,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般装处。 火紅的嫁衣襯著肌膚如雪方咆。 梳的紋絲不亂的頭發(fā)上票编,一...
    開封第一講書人閱讀 52,158評(píng)論 1 308
  • 那天褪储,我揣著相機(jī)與錄音,去河邊找鬼慧域。 笑死鲤竹,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的昔榴。 我是一名探鬼主播宛裕,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼论泛!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蛹屿,我...
    開封第一講書人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤屁奏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后错负,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體坟瓢,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年犹撒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了折联。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡识颊,死狀恐怖诚镰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情祥款,我是刑警寧澤清笨,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站刃跛,受9級(jí)特大地震影響抠艾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜桨昙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一检号、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蛙酪,春花似錦齐苛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春炊甲,著一層夾襖步出監(jiān)牢的瞬間泥彤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工卿啡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吟吝,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓颈娜,卻偏偏與公主長(zhǎng)得像剑逃,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子官辽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359

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

  • 1. 簡(jiǎn)介 1.1 什么是 MyBatis 蛹磺? MyBatis 是支持定制化 SQL、存儲(chǔ)過(guò)程以及高級(jí)映射的優(yōu)秀的...
    笨鳥慢飛閱讀 5,527評(píng)論 0 4
  • 前言 MyBatis是一個(gè)優(yōu)秀的持久層ORM框架俗或,它對(duì)jdbc的操作數(shù)據(jù)庫(kù)的過(guò)程進(jìn)行封裝,使開發(fā)者只需要關(guān)注SQL...
    AI喬治閱讀 641評(píng)論 0 5
  • 1.什么是Mybatis岁忘? 1辛慰、Mybatis 是一個(gè)半 ORM對(duì)象關(guān)系映射框架,內(nèi)部封裝了JDBC干像,開發(fā)時(shí)只需要...
    奇點(diǎn)一氪閱讀 397評(píng)論 0 6
  • MyBatis是一個(gè)優(yōu)秀的持久層ORM框架帅腌,它對(duì)jdbc的操作數(shù)據(jù)庫(kù)的過(guò)程進(jìn)行封裝,使開發(fā)者只需要關(guān)注SQL 本身...
    樓蘭King閱讀 672評(píng)論 0 5
  • 1.什么是動(dòng)態(tài)代理麻汰? 2.動(dòng)態(tài)代理的兩種實(shí)現(xiàn) JDK中的動(dòng)態(tài)代理: 通過(guò)反射類Proxy以及InvocationH...
    SHAN某人閱讀 313評(píng)論 0 1