Mybatis 文档篇 5:Java API

1 Directory Structure

本章中其他例子假设都遵循这样的目录结构:

/my_application
  /bin
  /devlib
  /lib                <-- MyBatis *.jar files go here.
  /src
    /org/myapp/
      /action
      /data           <-- MyBatis artifacts go here, including, Mapper Classes, XML Configuration, XML Mapping Files.
        /mybatis-config.xml
        /BlogMapper.java
        /BlogMapper.xml
      /model
      /service
      /view
    /properties       <-- Properties included in your XML Configuration go here.
  /test
    /org/myapp/
      /action
      /data
      /model
      /service
      /view
    /properties
  /web
    /WEB-INF
      /web.xml

2 SqlSessions

The primary Java interface for working with MyBatis is the SqlSession. Through this interface you can execute commands, get mappers and manage transactions.
使用 MyBatis 的主要接口是 SqlSession。通过这个接口,你可以执行命令,获取映射器和管理事务。

SqlSessions are created by a SqlSessionFactory instance. The SqlSessionFactory contains methods for creating instances of SqlSessions all different ways. The SqlSessionFactory itself is created by the SqlSessionFactoryBuilder that can create the SqlSessonFactory from XML, Annotations or hand coded Java configuration.
SqlSession 由一个 SqlSessionFactory 实例创建。SqlSessionFactory 包含创建 SqlSession 实例的几个不同的方式。SqlSessionFactory 是由 SqlSessionFactoryBuilder 创建的,它可以从 XML、注解或手动配置 Java 代码来创建。

NOTE When using MyBatis with a dependency injection framework like Spring or Guice, SqlSessions are created and injected by the DI framework so you don't need to use the SqlSessionFactoryBuilder or SqlSessionFactory.
注意:当 MyBatis 和一个依赖注入框架如 Spring、Guice等配合使用时,SqlSession 将被依赖注入框架创建和注入,所以你不必使用 SqlSessionFactoryBuilder 或 SqlSessionFactory。

2.1 SqlSessionFactoryBuilder

五个 build() 方法:

SqlSessionFactory build(InputStream inputStream)
SqlSessionFactory build(InputStream inputStream, String environment)
SqlSessionFactory build(InputStream inputStream, Properties properties)
SqlSessionFactory build(InputStream inputStream, String env, Properties props)
SqlSessionFactory build(Configuration config)

The first four methods are the most common, as they take an InputStream instance that refers to an XML document, or more specifically, the mybatis-config.xml file discussed above.
前四种方法是最常见的,他们使用了一个指向 XML 文件的 InputStream 实例,即 mybatis-config.xml 文件。

The optional parameters are environment and properties. Environment determines which environment to load, including the datasource and transaction manager. For example:
可选参数是 environment 和 properties。Environment 决定了要加载哪个环境,包括数据源和事务管理器。

<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
        ...
    <dataSource type="POOLED">
        ...
  </environment>
  <environment id="production">
    <transactionManager type="MANAGED">
        ...
    <dataSource type="JNDI">
        ...
  </environment>
</environments>

If you call a build method that takes the environment parameter, then MyBatis will use the configuration for that environment. Of course, if you specify an invalid environment, you will receive an error. If you call one of the build methods that does not take the environment parameter, then the default environment is uses (which is specified as default="development" in the example above).
如果你调用了一个参数为 environment 的 build 方法,那么 MyBatis 将使用 configuration 对象来配置这个 environment。当然,如果你指定了一个非法的 environment,你将会得到一个错误提示。如果你调用了不带 environment 参数的 build 方法,那么默认的 environment 会被使用(像上面例子中 default="development" 这样指定)。

If you call a method that takes a properties instance, then MyBatis will load those properties and make them available to your configuration. Those properties can be used in place of most values in the configuration using the syntax: ${propName}
如果你调用了一个参数为 properties 实例的 build 方法,那么 MyBatis 将加载那些 properties 并在配置中使它们可用。那些 properties 可以用 ${propName} 语法来代替配置中的大多数值。

Recall that properties can also be referenced from the mybatis-config.xml file, or specified directly within it. Therefore it's important to understand the priority.
回想一下,properties 也可以从 mybatis-config.xml 文件中被引用,或直接指定它。因此理解优先级是很重要的。

If a property exists in more than one of these places, MyBatis loads them in the following order.
如果一个属性在多个地方被定义,Mybatis 将按以下顺序加载:

  • Properties specified in the body of the properties element are read first,
    定义在 properties 元素体内的属性将被优先读取,

  • Properties loaded from the classpath resource or url attributes of the properties element are read second, and override any duplicate properties already specified,
    然后根据 properties 的 resource 属性读取类路径属性文件或者通过 properties 元素的 url 属性指定的路径读取属性文件,并且覆盖已读取的同名属性。

  • Properties passed as a method parameter are read last, and override any duplicate properties that may have been loaded from the properties body and the resource/url attributes.
    最后读取通过方法参数传递的属性,并且覆盖掉以上两种方式加载的同名属性。

Thus, the highest priority properties are those passed in as a method parameter, followed by resource/url attributes and finally the properties specified in the body of the properties element.
因此,优先级顺序从高到低依次是:通过方法参数传递的属性-->通过 resource/url 的路径下文件定义的属性-->properties 元素体中定义的属性

So to summarize, the first four methods are largely the same, but with overrides to allow you to optionally specify the environment and/or properties.
总结一下,前四种方法几乎相同,但由于覆盖机制,允许你可选地指定 environment 和/或 properties。

Here is an example of building a SqlSessionFactory from an mybatis-config.xml file.
这是个从 mybatis-config.xml 文件来创建 SqlSessionFactory 的例子:

String resource = "org/mybatis/builder/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);

Notice that we're making use of the Resources utility class, which lives in the org.apache.ibatis.io package. The Resources class, as its name implies, helps you load resources from the classpath, filesystem or even a web URL. Here's a quick list:
注意到我们使用的是 Resources 工具类,它在 org.apache.ibatis.io package。Resources 类,顾名思义,可以帮助你从类路径、文件系统甚至一个 web URL 中加载资源。下面是一个简表:

URL getResourceURL(String resource)
URL getResourceURL(ClassLoader loader, String resource)
InputStream getResourceAsStream(String resource)
InputStream getResourceAsStream(ClassLoader loader, String resource)
Properties getResourceAsProperties(String resource)
Properties getResourceAsProperties(ClassLoader loader, String resource)
Reader getResourceAsReader(String resource)
Reader getResourceAsReader(ClassLoader loader, String resource)
File getResourceAsFile(String resource)
File getResourceAsFile(ClassLoader loader, String resource)
InputStream getUrlAsStream(String urlString)
Reader getUrlAsReader(String urlString)
Properties getUrlAsProperties(String urlString)
Class classForName(String className)

The final build method takes an instance of Configuration. The Configuration class contains everything you could possibly need to know about a SqlSessionFactory instance. The Configuration class is useful for introspecting on the configuration, including finding and manipulating SQL maps (not recommended once the application is accepting requests). The configuration class has every configuration switch that you've learned about already, only exposed as a Java API.
最后一个 build 方法带一个 Configuration 实例的参数。Configuration 类包含了你需要了解 SqlSessionFactory 实例的所有内容。Configuration 类对于配置自查很有用,包含查找和操作 SQL 映射(一旦接收请求就不推荐使用)。作为一个 Java API,Configuration 类包含你已经了解过的所有配置的开关。

Here's a simple example of how to manually a Configuration instance and pass it to the build() method to create a SqlSessionFactory.
这是一个手动配置 Configuration 实例并将它传给 build() 方法来创建一个 SqlSessionFactory 的例子。

DataSource dataSource = BaseDataTest.createBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.setLazyLoadingEnabled(true);
configuration.setEnhancementEnabled(true);
configuration.getTypeAliasRegistry().registerAlias(Blog.class);
configuration.getTypeAliasRegistry().registerAlias(Post.class);
configuration.getTypeAliasRegistry().registerAlias(Author.class);
configuration.addMapper(BoundBlogMapper.class);
configuration.addMapper(BoundAuthorMapper.class);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(configuration);

2.2 SqlSessionFactory

SqlSessionFactory has six methods that are used to create SqlSession instances. In general, the decisions you'll be making when selecting one of these methods are:
SqlSessionFactory 有六个方法用来创建 SqlSession 实例。通常当选择这些方法时,你需要决定以下几点:

  • Transaction: Do you want to use a transaction scope for the session, or use auto-commit (usually means no transaction with most databases and/or JDBC drivers)?
    事务处理:你想要在 session 中使用事务作用域或使用自动提交吗?(通常意味着大多数数据库和/或 JDBC 驱动没有事务)

  • Connection: Do you want MyBatis to acquire a Connection from the configured DataSource for you, or do you want to provide your own?
    连接:你想要让 MyBatis 从配置的数据源中获得一个连接还是想要提供你自己的配置?

  • Execution: Do you want MyBatis to reuse PreparedStatements and/or batch updates (including inserts and deletes)?
    执行语句:你想要 MyBatis 重用 PreparedStatement 和/或批量更新吗(包含插入和删除)?

The set of overloaded openSession() method signatures allow you to choose any combination of these options that makes sense.
多个重载的 openSession() 允许你选择任意的选项组合来使用。

SqlSession openSession()
SqlSession openSession(boolean autoCommit)
SqlSession openSession(Connection connection)
SqlSession openSession(TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType,TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType)
SqlSession openSession(ExecutorType execType, boolean autoCommit)
SqlSession openSession(ExecutorType execType, Connection connection)
Configuration getConfiguration();

默认无参的 openSession() 方法创建一个具有以下特性的 SqlSession:

  • A transaction scope will be started (i.e. NOT auto-commit).
    开启一个事务作用域(即不会自动提交)。

  • A Connection object will be acquired from the DataSource instance configured by the active environment.
    将从由当前环境配置的 DataSource 实例获取一个 Connection 对象。

  • The transaction isolation level will be the default used by the driver or data source.
    事务隔离级别会使用驱动或数据源的默认配置。

  • No PreparedStatements will be reused, and no updates will be batched.
    PreparedStatement 不会被重用,也不会批量处理更新。

Most of the methods are pretty self explanatory. To enable auto-commit, pass a value of true to the optional autoCommit parameter. To provide your own connection, pass an instance of Connection to the connection parameter.
大多数的方法都是自解释性的。要开启自动提交,给可选的 autoCommit 参数传递 true 值就可以。要提供你自己的连接,给 connection 参数传递一个 Connection 的实例就可以。

Note that there's no override to set both the Connection and autoCommit, because MyBatis will use whatever setting the provided connection object is currently using.
注意没有一个重载方法是同时包含 Connection 和 autoCommit 的,因为 MyBatis 会使用提供的 connection 对象当前正在使用的所有设置。

MyBatis uses a Java enumeration wrapper for transaction isolation levels, called TransactionIsolationLevel, but otherwise they work as expected and have the 5 levels supported by JDBC (NONE, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE).
MyBatis 为事务隔离级别使用了一个 Java 枚举包装器,叫做 TransactionIsolationLevel,若不使用它,MyBatis 会按照预期的那样工作。JDBC 提供了 5 个级别(NONE, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE)。

The one parameter that might be new to you is ExecutorType. This enumeration defines 3 values:
有一个参数可能对你来说是陌生的:ExecutorType. 这个枚举类型定义了三个值:

  • ExecutorType.SIMPLE: This type of executor does nothing special. It creates a new PreparedStatement for each execution of a statement.
    ExecutorType.SIMPLE:这个执行器类型不做特殊的事。它为每个语句的执行创建一个新的 PreparedStatement。

  • ExecutorType.REUSE: This type of executor will reuse PreparedStatements.
    ExecutorType.REUSE:这个执行器类型将重用 PreparedStatements。

  • ExecutorType.BATCH: This executor will batch all update statements and demarcate them as necessary if SELECTs are executed between them, to ensure an easy-to-understand behavior.
    ExecutorType.BATCH:这个执行器会批量执行所有的更新语句,如果 select 语句在它们之间执行,必要时会将它们分开,以确保易读性。

NOTE There's one more method on the SqlSessionFactory that we didn't mention, and that is getConfiguration(). This method will return an instance of Configuration that you can use to introspect upon the MyBatis configuration at runtime.
注意:在 SqlSessionFactory 中还有一个我们没有提及的方法:getConfiguration()。这个方法会返回一个 Configuration 的实例,这样你就可以在运行期来自检 MyBatis 的配置。

NOTE If you've used a previous version of MyBatis, you'll recall that sessions, transactions and batches were all something separate. This is no longer the case. All three are neatly contained within the scope of a session. You need not deal with transactions or batches separately to get the full benefit of them.
注意:如果你使用的是 MyBatis 以前的版本,你要重新调用 openSession() ,因为旧版本的 session、transaction 和 batch 是分离开来的。新版本的话就不用了。因为它们三个都包含在了 session 作用域里。你不必再单独处理 transaction 或 batch 就能得到全部的效果。

2.3 SqlSession

As mentioned above, the SqlSession instance is the most powerful class in MyBatis. It is where you'll find all of the methods to execute statements, commit or rollback transactions and acquire mapper instances.
正如上面提到的,SqlSession 实例是 MyBatis 中最强大的类。在这里你可以找到所有执行语句、提交或回滚事务和获取映射器实例的方法。

There are over twenty methods on the SqlSession class, so let's break them up into more digestible groupings.
SqlSession 类中有超过 20 个方法,所以让我们来将它们拆分到更容易理解的组。

2.3.1 语句执行

These methods are used to execute SELECT, INSERT, UPDATE and DELETE statements that are defined in your SQL Mapping XML files. They are pretty self explanatory, each takes the ID of the statement and the Parameter Object, which can be a primitive (auto-boxed or wrapper), a JavaBean, a POJO or a Map.
这些方法被用来执行定义在 SQL 映射的 XML 文件中的 SELECT, INSERT, UPDATE 和 DELETE 语句。它们是自解释性的,每一个都有一个语句的 ID 和参数对象作为参数,这个参数对象可以是原生类型(自动装箱或包装类)、JavaBean、POJO 或 Map。

<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)

The difference between selectOne and selectList is only in that selectOne must return exactly one object or null (none). If any more than one, an exception will be thrown. If you don't' know how many objects are expected, use selectList. If you want to check for the existence of an object, you're better off returning a count (0 or 1).
selectOne 和 selectList 的区别仅在于在 selectOne 中必须只能返回一个对象或null。如果多于一个,会抛出异常。如果你不知道会返回多少对象,使用 selectList 。如果你想要检查一个对象是否存在,你最好返回一个数值(0 或 1)。

The selectMap is a special case in that it is designed to convert a list of results into a Map based on one of the properties in the resulting objects.
selectMap 是个特殊情况,它被设计用来转换一个结果列表到一个 Map,基于返回对象中的一个属性。

Because not all statements require a parameter, these methods are overloaded with versions that do not require the parameter object.
并不是所有的语句都需要参数,因此这些方法都重载了无参的形式。

The value returned by the insert, update and delete methods indicate the number of rows affected by the statement.
insert, update 和 delete 方法的返回值表示语句执行影响的行数。

<T> T selectOne(String statement)
<E> List<E> selectList(String statement)
<K,V> Map<K,V> selectMap(String statement, String mapKey)
int insert(String statement)
int update(String statement)
int delete(String statement)

Finally, there are three advanced versions of the select methods that allow you to restrict the range of rows to return, or provide custom result handling logic, usually for very large data sets.
最后,还有三个高级形式的查询方法,它们允许你限制结果返回的行数,或者提供自定义的结果处理逻辑,适用于大数据结果集。

<E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowbounds)
void select (String statement, Object parameter, ResultHandler<T> handler)
void select (String statement, Object parameter, RowBounds rowBounds, ResultHandler<T> handler)

The RowBounds parameter causes MyBatis to skip the number of records specified, as well as limit the number of results returned to some number. The RowBounds class has a constructor to take both the offset and limit, and is otherwise immutable.
RowBounds 参数会让 MyBatis 跳过指定的记录数,并限制返回结果的个数。RowBounds 类有一个带有 offset 和 limit 参数的构造方法,它们是不可变的。

int offset = 100;
int limit = 25;
RowBounds rowBounds = new RowBounds(offset, limit);

Different drivers are able to achieve different levels of efficiency in this regard. For the best performance, use result set types of SCROLL_SENSITIVE or SCROLL_INSENSITIVE (in other words: not FORWARD_ONLY).
在这点上,不同的驱动会达到不同级别的效率。性能最好的是使用 SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 类型的结果集(也就是非 FORWARD_ONLY 类型)。

The ResultHandler parameter allows you to handle each row however you like. You can add it to a List, create a Map, Set, or throw each result away and instead keep only rolled up totals of calculations. You can do pretty much anything with the ResultHandler, and it's what MyBatis uses internally itself to build result set lists.
ResultHandler 参数允许你按照你喜欢的方式处理每条结果。你可以将它添加到一个列表,或创建一个 Map 或 Set,或者丢弃每个结果只保留汇总的计算总数。你几乎可以用 ResultHandler 做任何事,并且它也是 MyBatis 内部使用用来构建结果集列表的。

Since 3.4.6, ResultHandler passed to a CALLABLE statement is used on every REFCURSOR output parameter of the stored procedure if there is any.
从 3.4.6 开始,传递给 CALLABLE 语句的 ResultHandler 用于存储过程的每个 REFCURSOR 输出参数(如果有的话)。

ResultHandler 接口是很简单的:

package org.apache.ibatis.session;
public interface ResultHandler<T> {
  void handleResult(ResultContext<? extends T> context);
}

The ResultContext parameter gives you access to the result object itself, a count of the number of result objects created, and a Boolean stop() method that you can use to stop MyBatis from loading any more results.
ResultContext 参数提供给你一个访问对象本身的通道、创建对象的个数、以及返回值为 Boolean 类型的 stop() 方法,使用该 stop() 方法你可以使 MyBatis 停止加载更多的结果。

使用 ResultHandler 有两个限制:

  • Data got from an method called with a ResultHandler will not be cached.
    使用 ResultHandler 调用方法得到的数据不会被缓存。

  • When using advanced resultmaps MyBatis will probably require several rows to build an object. If a ResultHandler is used you may be given an object whose associations or collections are not yet filled.
    当使用高级结果映射时,MyBatis 将可能获取几条结果来构建一个对象。如果一个 ResultHandler 使用了,你可能会得到一个 association 和 collection 尚未赋值的对象。

2.3.2 批量更新语句刷新

There is method for flushing(executing) batch update statements that stored in a JDBC driver class at any timing. This method can be used when you use the ExecutorType.BATCH as ExecutorType.
还有一个方法允许你在任何时候刷新(执行)存储在 JDBC 驱动类里的批量更新语句。当你使用 ExecutorType.BATCH 作为 ExecutorType 时,该方法可以被使用。

List<BatchResult> flushStatements()

2.3.3 事务控制

There are four methods for controlling the scope of a transaction. Of course, these have no effect if you've chosen to use auto-commit or if you're using an external transaction manager. However, if you're using the JDBC transaction manager, managed by the Connection instance, then the four methods that will come in handy are:
有四个方法用来控制事务的作用域。当然,如果你选择使用自动提交或外部的事务管理器,这就没有任何效果了。但是,如果你使用的是 JDBC 事务管理器,被 Connection 实例管理,那么这四个方法就可以派上用场:

void commit()
void commit(boolean force)
void rollback()
void rollback(boolean force)

By default MyBatis does not actually commit unless it detects that the database has been changed by a call to insert, update or delete. If you've somehow made changes without calling these methods, then you can pass true into the commit and rollback methods to guarantee that it will be committed (note, you still can't force a session in auto-commit mode, or one that is using an external transaction manager).
默认情况下,MyBatis 不会自动提交事务,除非它发现数据库已经通过调用 insert, update 或 delete 改变了。如果你改变了数据库但没有调用这些方法,那么你可以传一个 true 值到 commit 和 rollback 方法以保证它会被提交(注意,在自动提交模式下或使用外部事务管理器情况下,设置 force 对 session 无效)。

Most of the time you won't have to call rollback(), as MyBatis will do that for you if you don't call commit. However, if you need more fine grained control over a session where multiple commits and rollbacks are possible, you have the rollback option there to make that possible.
大多数时候你不用调用 rollback(), 因为 MyBatis 将在你不调用 commit 的情况下自动完成回滚操作。但是,如果你需要在多个提交和回滚的情况下获得更好的粒度控制,你可以使用回滚操作来达到目的。

2.3.4 本地缓存

MyBatis uses two caches: a local cache and a second level cache.
MyBatis 使用两种缓存:本地缓存和二级缓存。

Each time a new session is created MyBatis creates a local cache and attaches it to the session. Any query executed within the session will be stored in the local cache so further executions of the same query with the same input parameters will not hit the database. The local cache is cleared upon update, commit, rollback and close.
每创建一个新的 session ,MyBatis 都会创建一个本地缓存并将其与该 session 相关联。任何该 session 期间的查询的执行结果都会被存储到这个本地缓存,之后带有相同参数的相同查询的执行不会再去访问数据库。本地缓存会被更新、提交、回滚和关闭操作所清空。

By default local cache data is used for the whole session duration. This cache is needed to resolve circular references and to speed up repeated nested queries, so it can never be completely disabled but you can configure the local cache to be used just for the duration of an statement execution by setting localCacheScope=STATEMENT.
本地缓存数据默认可以在整个 session 期间被使用。这一缓存被用来解决循环引用和加速重复嵌套查询,所以它不能被完全关闭但你可以通过设置 localCacheScope=STATEMENT 来配置本地缓存,以使其仅在语句执行期间可用。

Note that when the localCacheScope is set to SESSION, MyBatis returns references to the same objects which are stored in the local cache. Any modification of returned object (lists etc.) influences the local cache contents and subsequently the values which are returned from the cache in the lifetime of the session. Therefore, as best practice, do not to modify the objects returned by MyBatis.
注意当 localCacheScope 被设置为 SESSION,MyBatis 会返回存储在本地缓存中的相同对象的引用。任何对返回对象的修改(list 等)都会影响本地缓存的内容,进而影响存活在 session 生命周期内的缓存中返回的值。因此,为了最好的体验,不要修改 MyBatis 返回的对象。

You can clear the local cache at any time calling:
你可以在任何时候调用来清空本地缓存:

void clearCache()

2.3.5 确保 SqlSession 关闭

void close()

The most important thing you must ensure is that you close any sessions that you open. The best way to ensure this is to use the following unit of work pattern:
有一件你必须确保的很重要的事是:要关闭你打开的任何 session。保证这一点的最好的方法是使用下面这个工作模式:

SqlSession session = sqlSessionFactory.openSession();
try {
    session.insert(...);
    session.update(...);
    session.delete(...);
    session.commit();
} finally {
    session.close();
}

Or, If you are using jdk 1.7+ and MyBatis 3.2+, you can use the try-with-resources statement:
或者,如果你使用的是 jdk 1.7 以上的版本和 MyBatis 3.2 以上的版本,你可以使用 try-with-resources 语句:

try (SqlSession session = sqlSessionFactory.openSession()) {
    session.insert(...);
    session.update(...);
    session.delete(...);
    session.commit();
}

NOTE Just like SqlSessionFactory, you can get the instance of Configuration that the SqlSession is using by calling the getConfiguration() method.
注意:和 SqlSessionFactory 一样,你可以通过调用 getConfiguration() 方法得到 SqlSession 正在使用的 Configuration 的实例。

Configuration getConfiguration()

2.3.6 使用映射器

<T> T getMapper(Class<T> type)

While the various insert, update, delete and select methods above are powerful, they are also very verbose, not type safe and not as helpful to your IDE or unit tests as they could be.
尽管上面提到的各种各样的 insert, update, delete 和 select 语句非常强大,但他们也是很冗长的、非类型安全的,并且对于你的 IDE 或单元测试可能没那么有帮助。

Therefore, a more common way to execute mapped statements is to use Mapper classes. A mapper class is simply an interface with method definitions that match up against the SqlSession methods. The following example class demonstrates some method signatures and how they map to the SqlSession.
因此,执行映射语句更加常用的方式是使用 Mapper 类。Mapper 类是一个简单的接口,声明了与 SqlSession 的方法相匹配的方法。下面的例子展示了一些方法签名以及它们是如何映射到 SqlSession 的。

public interface AuthorMapper {
  // (Author) selectOne("selectAuthor", 5);
  Author selectAuthor(int id);
  
  // (List<Author>) selectList("selectAuthors")
  List<Author> selectAuthors();
  
  // (Map<Integer,Author>) selectMap("selectAuthors", "id")
  @MapKey("id")
  Map<Integer, Author> selectAuthors();
 
  // insert("insertAuthor", author)
  int insertAuthor(Author author);
  
  // updateAuthor("updateAuthor", author)
  int updateAuthor(Author author);
  
  // delete("deleteAuthor", 5)
  int deleteAuthor(int id);
}

In a nutshell, each Mapper method signature should match that of the SqlSession method that it's associated to, but without the String parameter ID. Instead, the method name must match the mapped statement ID.
简言之,每个 Mapper 方法签名都应该匹配关联的 SqlSession 方法,而不需要 String 类型的参数 ID。相反,方法名必须匹配映射语句的 ID。

In addition, the return type must match that of the expected result type for single results or an array or collection for multiple results. All of the usual types are supported, including: Primitives, Maps, POJOs and JavaBeans.
除此之外,返回值类型必须匹配,单返回值时为期望的返回类型,多返回值时为数组或集合。支持所有的常用类型,包括:原生类型、Map、POJO 和 JavaBean。

NOTE Mapper interfaces do not need to implement any interface or extend any class. As long as the method signature can be used to uniquely identify a corresponding mapped statement.
注意:Mapper 接口不需要实现任何接口或者继承任何类。只要其方法签名可以唯一标识一个对应的映射语句。

NOTE Mapper interfaces can extend other interfaces. Be sure that you have the statements in the appropriate namespace when using XML binding to Mapper interfaces. Also, the only limitation is that you cannot have the same method signature in two interfaces in a hierarchy (a bad idea anyway).
注意:Mapper 接口可以继承其他接口。确保当你使用 XML 绑定到 Mapper 接口时,你的语句在一个合适的命名空间。而且,唯一的限制是你不能在两个有继承关系的接口中有相同的方法签名。

You can pass multiple parameters to a mapper method. If you do, they will be named by the literal "param" followed by their position in the parameter list by default, for example: #{param1}, #{param2} etc. If you wish to change the name of the parameters (multiple only), then you can use the @Param("paramName") annotation on the parameter.
你可以传递多个参数到一个 mapper 方法。如果你这样做了,默认情况下它们将通过 “param” 字符串加其在参数列表中的位置来命名,例如,#{param1}, #{param2} 等。如果你希望改变参数的名称(仅多参数情况下),你可以在参数上使用 @Param("paramName") 注解。

You can also pass a RowBounds instance to the method to limit query results.
你也可以给方法传递一个 RowBounds 实例来限制查询结果。

2.3.7 映射器注解

Since the very beginning, MyBatis has been an XML driven framework. The configuration is XML based, and the Mapped Statements are defined in XML. With MyBatis 3, there are new options available. MyBatis 3 builds on top of a comprehensive and powerful Java based Configuration API. This Configuration API is the foundation for the XML based MyBatis configuration, as well as the new Annotation based configuration. Annotations offer a simple way to implement simple mapped statements without introducing a lot of overhead.
从最开始,MyBatis 是一个 XML 驱动的框架。配置是基于 XML 的,映射语句定义在 XML 中。到了 MyBatis 3,就有了新的选择。MyBatis 3 构建在一个全面且强大的基于 Java 语言的 Configuration API 之上。这个 Configuration API 是基于 XML 的 MyBatis 配置的基础,也是新的基于注解配置的基础。注解提供了一个简单的方式来实现简单的语句映射,而不用引入大量的开销。

NOTE Java Annotations are unfortunately limited in their expressiveness and flexibility. Despite a lot of time spent in investigation, design and trials, the most powerful MyBatis mappings simply cannot be built with Annotations – without getting ridiculous that is. C# Attributes (for example) do not suffer from these limitations, and thus MyBatis.NET will enjoy a much richer alternative to XML. That said, the Java Annotation based configuration is not without its benefits.
注意:不幸的是,Java 注解限制在其表达力和灵活性上。尽管在调查、设计和实验上花费了很多时间,最强大的 MyBatis 映射不能简单地用注解来构建。比如,C# 属性就没有这些限制,因此 MyBatis.NET 就会有比 XML 更多的选择。也就是说,基于Java 注解的配置并不是没有它的好处。

2.3.7.1 Mapper 注解
  • @CacheNamespace
    注解目标:Class
    等价 XML 元素:<cache>

    Configures the cache for the given namespace (i.e. class). Attributes: implementation, eviction, flushInterval, size, readWrite, blocking, properties.
    为指定的命名空间(即类)配置缓存。属性有: implementation, eviction, flushInterval, size, readWrite, blocking, properties。

  • @Property
    注解目标:N/A
    等价 XML 元素:<property>

    Specifies the property value or placeholder(can replace by configuration properties that defined at the mybatis-config.xml). Attributes: name, value. (Available on MyBatis 3.4.2+)
    指定参数值或占位符(可以被通过定义在 mybatis-config.xml 中的配置属性覆盖)。属性有:name,value。(MyBatis 3.4.2 以上可用)

  • @CacheNamespaceRef
    注解目标:Class
    等价 XML 元素:<cacheRef>

    References the cache of another namespace to use. Note that caches declared in an XML mapper file are considered a separate namespace, even if they share the same FQCN. Attributes: value and name. If you use this annotation, you should be specified either value or name attribute. For the value attribute specify a java type indicating the namespace(the namespace name become a FQCN of specified java type), and for the name attribute(this attribute is available since 3.4.2) specify a name indicating the namespace.
    引用另一个命名空间的缓存来使用。注意声明在一个 XML 映射文件中的缓存被认为是一个独立的命名空间,即使它们共享相同的 FQCN(完全限定类名)。属性有:value 和 name。如果你使用这个注解,你应该指定 value 或 name 属性之一。value 属性指定一个表示命名空间的 java 类型(命名空间的名称是指定 Java 类型的 FQCN),name 属性(从 3.4.2 开始该属性可用)指定一个表示这个命名空间的名称。

  • @ConstructorArgs
    注解目标:Method
    等价 XML 元素:<constructor>

    Collects a group of results to be passed to a result object constructor. Attributes: value, which is an array of Args.
    收集一组结果传递到一个结果对象的构造方法。属性有:value,为 Arg 构成的数组。

  • @Arg
    注解目标:N/A
    等价 XML 元素:<arg>、<idArg>

    A single constructor argument that is part of a ConstructorArgs collection. Attributes: id, column, javaType, jdbcType, typeHandler, select, resultMap. The id attribute is a boolean value that identifies the property to be used for comparisons, similar to the <idArg> XML element.
    单个构造方法的参数,是 ConstructorArg 的一部分。属性有: id, column, javaType, jdbcType, typeHandler, select, resultMap。id 属性是一个布尔值,它唯一标识用来比较的属性,类似于 XML 元素 <idArg>。

  • @TypeDiscriminator
    注解目标:Method
    等价 XML 元素:<discriminator>

    A group of value cases that can be used to determine the result mapping to perform. Attributes: column, javaType, jdbcType, typeHandler, cases. The cases attribute is an array of Cases.
    一组 case 值,被用来决定结果映射如何表现。属性有:column, javaType, jdbcType, typeHandler, cases。cases 属性是一个由 case 构成的数组。

  • @Case
    注解目标:N/A
    等价 XML 元素:<case>

    A single case of a value and its corresponding mappings. Attributes: value, type, results. The results attribute is an array of Results, thus this Case Annotation is similar to an actual ResultMap, specified by the Results annotation below.
    单个 case 值和它对应的映射。属性有:value, type, results。results 属性是结果数组,因此这个 Case 注解和一个实际的 ResultMap 类似,通过下面的 Results 注解指定。

  • @Results
    注解目标:Method
    等价 XML 元素:<resultMap>

    A list of Result mappings that contain details of how a particular result column is mapped to a property or field. Attributes: value, id. The value attribute is an array of Resultannotations. The id attribute is the name of the result mapping.
    结果映射列表,包含了一个特定结果列如何映射到一个属性或者字段的细节。属性有:value,id。value 属性是 Result 注解的数组。id 属性是结果映射的名称。

  • @Result
    注解目标:N/A
    等价 XML 元素: <result>、<id>

    A single result mapping between a column and a property or field. Attributes: id, column, property, javaType, jdbcType, typeHandler, one, many. The id attribute is a boolean value that indicates that the property should be used for comparisons (similar to <id> in the XML mappings). The one attribute is for single associations, similar to <association>, and the many attribute is for collections, similar to <collection>. They are named as they are to avoid class naming conflicts.
    单个列和属性或字段之间的结果映射。属性有: id, column, property, javaType, jdbcType, typeHandler, one, many。id 属性是一个布尔值,表示用来比较的属性(同 XML 映射中的 <id> 类似 )。one 属性用来表示单个关联,同 <association>,many 属性用来表示集合,同 <collection>。它们这样命名是为了避免类名冲突。

  • @One
    注解目标:N/A
    等价 XML 元素:<association>

    A mapping to a single property value of a complex type. Attributes: select, which is the fully qualified name of a mapped statement (i.e. mapper method) that can load an instance of the appropriate type, fetchType, which supersedes the global configuration parameter lazyLoadingEnabled for this mapping. NOTE You will notice that join mapping is not supported via the Annotations API. This is due to the limitation in Java Annotations that does not allow for circular references.
    复杂类型的单个属性值映射。属性有:select,映射语句的完全限定名(即映射方法),可以加载合适类型的实例。fetchType 会覆盖全局的配置参数 lazyLoadingEnabled 。注意:你会注意到注解 API 不支持连接查询的映射。这是因为 Java 注解不允许循环引用的限制。

  • @Many
    注解目标:N/A
    等价 XML 元素:<collection>

    A mapping to a collection property of a complex type. Attributes: select, which is the fully qualified name of a mapped statement (i.e. mapper method) that can load a collection of instances of the appropriate types, fetchType, which supersedes the global configuration parameter lazyLoadingEnabled for this mapping. NOTE You will notice that join mapping is not supported via the Annotations API. This is due to the limitation in Java Annotations that does not allow for circular references.
    复杂类型的集合属性的映射。属性有:select,映射语句的完全限定名(即映射方法),可以加载合适类型的集合实例。fetchType 会覆盖全局的配置参数 lazyLoadingEnabled 。注意:你会注意到注解 API 不支持连接查询的映射。这是因为 Java 注解不允许循环引用的限制。

  • @MapKey
    注解目标:Method
    等价 XML 元素:无

    This is used on methods which return type is a Map. It is used to convert a List of result objects as a Map based on a property of those objects. Attributes: value, which is a property used as the key of the map.
    用于返回值类型是 Map 的方法,它被用来将结果对象的 List 转换成一个 Map,基于对象的一个属性。属性有:value,作为 Map 的 key 的一个属性。

  • @Options
    注解目标:Method
    等价 XML 元素:映射语句的属性。

    This annotation provides access to the wide range of switches and configuration options that are normally present on the mapped statement as attributes. Rather than complicate each statement annotation, the Options annotation provides a consistent and clear way to access these. Attributes: useCache=true, flushCache=FlushCachePolicy.DEFAULT, resultSetType=FORWARD_ONLY, statementType=PREPARED, fetchSize=-1, timeout=-1, useGeneratedKeys=false, keyProperty="id", keyColumn="", resultSets="". It's important to understand that with Java Annotations, there is no way to specify null as a value. Therefore, once you engage the Options annotation, your statement is subject to all of the default values. Pay attention to what the default values are to avoid unexpected behavior.Note that keyColumn is only required in certain databases (like Oracle and PostgreSQL). See the discussion about keyColumn and keyProperty above in the discussion of the insert statement for more information about allowable values in these attributes.
    这个注解提供了大范围的交换和配置选项的方法,通常作为属性被放在映射语句中。Options 注解提供了通俗易懂的方式来访问它们。属性有:useCache=true, flushCache=FlushCachePolicy.DEFAULT, resultSetType=FORWARD_ONLY, statementType=PREPARED, fetchSize=-1, timeout=-1, useGeneratedKeys=false, keyProperty="id", keyColumn="", resultSets=""。有一点很重要,Java 注解中没有办法指定 null 值。一旦你使用了 Options 注解,你的语句就会受制于所有的默认值。关注默认值以避免不可预期的行为。注意 keyColumn 只在特定的数据库中使用(像 Oracle 和 PostgreSQL)。keyColumn 和 keyProperty 请查看上面讨论的 insert 语句部分。

  • @Insert、@Update、@Delete、@Select
    注解目标:Method
    等价 XML 元素:<insert>、<update>、<delete>、<select>

    Each of these annotations represents the actual SQL that is to be executed. They each take an array of strings (or a single string will do). If an array of strings is passed, they are concatenated with a single space between each to separate them. This helps avoid the "missing space" problem when building SQL in Java code. However, you're also welcome to concatenate together a single string if you like. Attributes: value, which is the array of Strings to form the single SQL statement.
    这些注解分别代表了实际要被执行的 SQL。它们每个都带有一个字符串数组参数(或单个字符串)。如果传入一个字符串数组,它们之间会以一个空格分隔。这有效避免了在 Java 代码中构建 SQL 时的“空格丢失”问题。但如果你想的话,你也可以手动连接好字符串。属性有:value,为组成单个 SQL 语句的 String 数组。

  • @InsertProvider、@UpdateProvider、@DeleteProvider、@SelectProvider
    注解目标:Method
    等价 XML 元素:<insert>、<update>、<delete>、<select>

    Allows for creation of dynamic SQL. These alternative SQL annotations allow you to specify a class and a method name that will return the SQL to run at execution time (Since 3.4.6, you can specify the CharSequence instead of String as a method return type). Upon executing the mapped statement, MyBatis will instantiate the class, and execute the method, as specified by the provider. You can pass objects that passed to arguments of a mapper method, "Mapper interface type" and "Mapper method" via the ProviderContext(available since MyBatis 3.4.5 or later) as method argument. (In MyBatis 3.4 or later, it's allow multiple parameters) Attributes: type, method. The type attribute is a class. The method is the name of the method on that class. NOTE Following this section is a discussion about the class, which can help build dynamic SQL in a cleaner, easier to read way.
    允许创建动态 SQL。这些可选的 SQL 注解允许你指定一个类和在运行期间返回 SQL的一个方法名称(从 3.4.6 开始,你可以指定 CharSequence 来替代 String 作为一个方法返回类型)。当执行映射语句时,MyBatis 会实例化 provider 指定的类,并且执行 provider 指定的方法。你可以传递已经传递给映射器方法的参数的对象,"Mapper interface type" 和 "Mapper method" 经由 ProviderContext (从 3.4.5 及以上可用)作为参数。(在 MyBatis 3.4 及之后的版本,允许多参数)。属性有:type,method。type 属性是一个类。method 属性是该类中的方法名。注意:接下来的章节会讨论该类,可以帮助你更轻松地构建动态 SQL。

  • @Param
    注解目标:Parameter
    等价 XML 元素:N/A

    If your mapper method takes multiple parameters, this annotation can be applied to a mapper method parameter to give each of them a name. Otherwise, multiple parameters will be named by their position prefixed with "param" (not including any RowBounds parameters). For example #{param1}, #{param2} etc. is the default. With @Param("person"), the parameter would be named #{person}.
    如果你的映射器方法有多个参数,这个注解可以使用在映射方法上来为每个参数指定名字。否则,多个参数默认将会以 "param" 为前缀加它们的位置来命名(不包含任何 RowBounds 参数)。例如,#{param1}, #{param2}等。使用 @Param("person"),参数会被命名为 #{person}。

  • @SelectKey
    注解目标:Method
    等价 XML 元素:<selectKey>

    This annotation duplicates the <selectKey> functionality for methods annotated with @Insert, @InsertProvider, @Update, or @UpdateProvider. It is ignored for other methods. If you specify a @SelectKey annotation, then MyBatis will ignore any generated key properties set via the @Options annotation, or configuration properties. Attributes: statement an array of strings which is the SQL statement to execute, keyProperty which is the property of the parameter object that will be updated with the new value, before which must be either true or false to denote if the SQL statement should be executed before or after the insert, resultType which is the Java type of the keyProperty, and statementType is a type of the statement that is any one of STATEMENT, PREPARED or CALLABLE that is mapped to Statement, PreparedStatement and CallableStatement respectively. The default is PREPARED.
    这个注解同 <selectKey> 功能完全一致,用在注解了 @Insert, @InsertProvider, @Update, 或 @UpdateProvider 的方法上。它会被其他方法忽略。如果你指定了一个 @SelectKey 注解,那么 MyBatis 会忽略任何通过 @Options 注解或配置属性设置的主键生成。属性有:statement(一个要被执行的 SQL 语句的字符串数组),keyProperty (将会被更新的参数对象的属性),before (true 或 false,用来指明 SQL 语句应该在 insert 之前还是之后执行),resultType (keyProperty 的 Java 类型),以及 statementType (语句的类型,值为STATEMENT, PREPARED 或 CALLABLE ,分别对应Statement, PreparedStatement 和 CallableStatement,默认值为 PREPARED)。

  • @ResultMap
    注解目标:Method
    等价 XML 元素:N/A

    This annotation is used to provide the id of a <resultMap> element in an XML mapper to a @Select or @SelectProvider annotation. This allows annotated selects to reuse resultmaps that are defined in XML. This annotation will override any @Results or @ConstructorArgs annotation if both are specified on an annotated select.
    该注解被用来给 @Select 或 @SelectProvider 注解提供 XML 映射中 的 <resultMap> 的 id。它允许被注解的 select 来重用定义在 XML 中的 ResultMap。这个注解会覆盖掉任何的 @Results 或 @ConstructorArgs 注解,如果两者都被注解在同一个 select 中。

  • @ResultType
    注解目标:Method
    等价 XML 元素:N/A

    This annotation is used when using a result handler. In that case, the return type is void so MyBatis must have a way to determine the type of object to construct for each row. If there is an XML result map, use the @ResultMap annotation. If the result type is specified in XML on the <select> element, then no other annotation is necessary. In other cases, use this annotation. For example, if a @Select annotated method will use a result handler, the return type must be void and this annotation (or @ResultMap) is required. This annotation is ignored unless the method return type is void.
    这个注解在使用一个结果处理器的时候使用。在此情况下,返回类型是 void 所以 MyBatis 必须有一个方法来决定为每条结果构造一个对象类型。如果有一个 XML 结果映射,使用 @ResultMap 注解。如果返回类型在 XML 中的 <select> 元素上指定了,那就不需要注解。除此之外,就要使用该注解。例如,如果一个注解了 @Select 的方法要使用一个结果处理器,那么返回类型必须为 void 并且这个注解(或 @ResultMap)是必须的。这个注解仅在方法返回类型是 void 情况下生效。

  • @Flush
    注解目标:Method
    等价 XML 元素:N/A

    If this annotation is used, it can be called the SqlSession#flushStatements() via method defined at a Mapper interface.(MyBatis 3.3 or above)
    如果使用该注解,它会通过定义在 Mapper 接口中的方法来调用 SqlSession#flushStatements() 。(MyBatis 3.3 及以上)

2.3.7.2 Mapper 注解举例
  1. 使用@SelectKey 注解在 insert 前从序列中获取值:
@Insert("insert into table3 (id, name) values(#{nameId}, #{name})")
@SelectKey(statement="call next value for TestSequence", keyProperty="nameId", before=true, resultType=int.class)
int insertTable3(Name name);
  1. 使用@SelectKey 注解在 insert 后获取主键的值:
@Insert("insert into table2 (name) values(#{name})")
@SelectKey(statement="call identity()", keyProperty="nameId", before=false, resultType=int.class)
int insertTable2(Name name);
  1. 使用 @Flush 注解调用 SqlSession#flushStatements() 方法:
@FlushList<BatchResult> 
flush();
  1. 通过指定 @Results 注解的 id 属性来命名一个 ResultMap:
@Results(id = "userResult", value = {
  @Result(property = "id", column = "uid", id = true),
  @Result(property = "firstName", column = "first_name"),
  @Result(property = "lastName", column = "last_name")
})
@Select("select * from users where id = #{id}")
User getUserById(Integer id);

@Results(id = "companyResults")
@ConstructorArgs({
  @Arg(property = "id", column = "cid", id = true),
  @Arg(property = "name", column = "name")
})
@Select("select * from company where id = #{id}")
Company getCompanyById(Integer id);
  1. 单一参数使用 @SelectProvider 注解:
@SelectProvider(type = UserSqlBuilder.class, method = "buildGetUsersByName")
List<User> getUsersByName(String name);

class UserSqlBuilder {
  public static String buildGetUsersByName(final String name) {
    return new SQL(){{
      SELECT("*");
      FROM("users");
      if (name != null) {
        WHERE("name like #{value} || '%'");
      }
      ORDER_BY("id");
    }}.toString();
  }
}
  1. 多个参数使用 @SelectProvider 注解:
@SelectProvider(type = UserSqlBuilder.class, method = "buildGetUsersByName")
List<User> getUsersByName(@Param("name") String name, @Param("orderByColumn") String orderByColumn);

class UserSqlBuilder {
  // 如果不使用 @Param,参数命名同映射器方法
  public static String buildGetUsersByName(final String name, final String orderByColumn) {
    return new SQL(){{
      SELECT("*");
      FROM("users");
      WHERE("name like #{name} || '%'");
      ORDER_BY(orderByColumn);
    }}.toString();
  }

  // 如果使用 @Param,你可以只定义要使用的参数
  public static String buildGetUsersByName(@Param("orderByColumn") final String orderByColumn) {
    return new SQL(){{
      SELECT("*");
      FROM("users");
      WHERE("name like #{name} || '%'");
      ORDER_BY(orderByColumn);
    }}.toString();
  }
}

最后

说明:MyBatis 官网提供了简体中文的翻译,但个人觉得较为生硬,甚至有些地方逻辑不通,于是自己一个个重新敲着翻译的(都不知道哪里来的自信...),有些地方同官网翻译有出入,有些倔强地保留了自己的,有的实在别扭则保留了官网的,这些都会在实践中一一更正。鉴于个人英文能力有限,文章中保留了官方文档原英文介绍(个别地方加以调整修剪),希望有缘看到这里的朋友们能够有自己的理解,不会被我可能错误或不合理的翻译带跑偏(〃'▽'〃),欢迎指正!

当前版本:mybatis-3.5.0
官网文档:MyBatis
官网翻译:MyBatis 简体中文
项目实践:MyBatis Learn

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352