Mybatis源码学习-开篇

Mybatis源码学习-开篇

学习源码前,需要知道框架解决了什么问题,基本用法是什么,然后再去深入研究其内部实现,并能举一反三,灵活运用

mybatis是什么

MyBatis is a first class persistence framework with support for custom SQL, stored procedures and advanced mappings. MyBatis eliminates almost all of the JDBC code and manual setting of parameters and retrieval of results. MyBatis can use simple XML or Annotations for configuration and map primitives, Map interfaces and Java POJOs (Plain Old Java Objects) to database records. -- 摘自官方文档(https://mybatis.org/mybatis-3/index.html

简单翻一下:

  • 持久层框架

  • 支持自定义SQL、存储过程和高级映射

  • 隐藏了大部分JDBC的代码细节(模板代码、设置参数以及返回值封装)

  • 支持XML以及Java注解配置

  • 支持数据库记录与Java对象的映射

怎么使用

在Maven项目pom文件中添加一下依赖,版本取官方最新版本即可

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>x.x.x</version>
</dependency>

开天辟地-SqlSessionFactory

mybatis所有的数据库操作由SqlSession会话进行管理,而要实例化SqlSession需要使用SqlSessionFactory,创建SqlSessionFactory是由SqlSessionFactoryBuilder产生

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

初始化配置

  • xml

mybatis初始化的配置支持xml协议,比如设置数据源、事务管理器等,以下只是一小部分

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>
  • code

当然,mybatis也支持通过代码的方式进行配置的初始化操作

DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

执行-SqlSession

真正执行数据库操作的全部由SqlSession来掌控,而SqlSession想要执行真正的数据库操作,肯定离不开SQL语句,mybatis支持xml配置形式以及注解形式来编写你的SQL语句,不过由于xml配置的方式更加灵活而且强大,支持更复杂的SQL聚合,所以官方还是建议以xml的形式来组织你的SQL以及对象的映射关系

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper">
  <select id="selectBlog" resultType="Blog">
    select * from Blog where id = #{id}
  </select>
</mapper>
package org.mybatis.example;
public interface BlogMapper {
  @Select("SELECT * FROM blog WHERE id = #{id}")
  Blog selectBlog(int id);
}

调用执行有两种方式,一种是通过命名空间(namespace)值找到对应的映射SQL,一种是基于mybatis对映射关系的接口代理,后者更加安全和方便

Blog blog = session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);

BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);

初始化配置

mybatis核心初始化配置文件包含以下内容:

properties

定义了一些可复用的变量

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>

例如:下面美元符号中引用的变量即为上面定义的部分

<dataSource type="POOLED">
  <property name="driver" value="${driver}"/>
  <property name="url" value="${url}"/>
  <property name="username" value="${username}"/>
  <property name="password" value="${password}"/>
</dataSource>

属性文件加载顺序

  • 指定属性文件
  • 类路径或者指定url地址获取的属性
  • 方法中进行传递的属性

以上按照加载顺序加载入内存,并且后加载的存在相同属性名称的会覆盖之前加载的属性值

默认值:以下形式冒号后面为当前属性未赋值时的默认值

${username:ut_user}

前提是需要进行配置开启,默认配置是不支持默认值的方式

<properties resource="org/mybatis/example/config.properties">
  <!-- ... -->
  <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/> <!-- Enable this feature -->
</properties>

默认值加载是进行分隔符进行拆分,解析进行复制,可能会存在符号冲突,因此如果存在默认值中存在冒号值,你需要修改分隔符

<properties resource="org/mybatis/example/config.properties">
  <!-- ... -->
  <property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="?:"/> <!-- Change default value of separator -->
</properties>
<dataSource type="POOLED">
  <!-- ... -->
  <property name="username" value="${db:username?:ut_user}"/>
</dataSource>

settings

mybatis运行时设置参数,如下所示,摘自官方文档(https://mybatis.org/mybatis-3/configuration.html

Setting Description Valid Values Default
cacheEnabled Globally enables or disables any caches configured in any mapper under this configuration. true | false true
lazyLoadingEnabled Globally enables or disables lazy loading. When enabled, all relations will be lazily loaded. This value can be superseded for a specific relation by using the fetchType attribute on it. true | false false
aggressiveLazyLoading When enabled, any method call will load all the lazy properties of the object. Otherwise, each property is loaded on demand (see also lazyLoadTriggerMethods). true | false false (true in ≤3.4.1)
multipleResultSetsEnabled Allows or disallows multiple ResultSets to be returned from a single statement (compatible driver required). true | false true
useColumnLabel Uses the column label instead of the column name. Different drivers behave differently in this respect. Refer to the driver documentation, or test out both modes to determine how your driver behaves. true | false true
useGeneratedKeys Allows JDBC support for generated keys. A compatible driver is required. This setting forces generated keys to be used if set to true, as some drivers deny compatibility but still work (e.g. Derby). true | false False
autoMappingBehavior Specifies if and how MyBatis should automatically map columns to fields/properties. NONE disables auto-mapping. PARTIAL will only auto-map results with no nested result mappings defined inside. FULL will auto-map result mappings of any complexity (containing nested or otherwise). NONE, PARTIAL, FULL PARTIAL
autoMappingUnknownColumnBehavior Specify the behavior when detects an unknown column (or unknown property type) of automatic mapping target.NONE: Do nothingWARNING: Output warning log (The log level of 'org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' must be set to WARN)FAILING: Fail mapping (Throw SqlSessionException) NONE, WARNING, FAILING NONE
defaultExecutorType Configures the default executor. SIMPLE executor does nothing special. REUSE executor reuses prepared statements. BATCH executor reuses statements and batches updates. SIMPLE REUSE BATCH SIMPLE
defaultStatementTimeout Sets the number of seconds the driver will wait for a response from the database. Any positive integer Not Set (null)
defaultFetchSize Sets the driver a hint as to control fetching size for return results. This parameter value can be override by a query setting. Any positive integer Not Set (null)
defaultResultSetType Specifies a scroll strategy when omit it per statement settings. (Since: 3.5.2) FORWARD_ONLY | SCROLL_SENSITIVE | SCROLL_INSENSITIVE | DEFAULT(same behavior with 'Not Set') Not Set (null)
safeRowBoundsEnabled Allows using RowBounds on nested statements. If allow, set the false. true | false False
safeResultHandlerEnabled Allows using ResultHandler on nested statements. If allow, set the false. true | false True
mapUnderscoreToCamelCase Enables automatic mapping from classic database column names A_COLUMN to camel case classic Java property names aColumn. true | false False
localCacheScope MyBatis uses local cache to prevent circular references and speed up repeated nested queries. By default (SESSION) all queries executed during a session are cached. If localCacheScope=STATEMENT local session will be used just for statement execution, no data will be shared between two different calls to the same SqlSession. SESSION | STATEMENT SESSION
jdbcTypeForNull Specifies the JDBC type for null values when no specific JDBC type was provided for the parameter. Some drivers require specifying the column JDBC type but others work with generic values like NULL, VARCHAR or OTHER. JdbcType enumeration. Most common are: NULL, VARCHAR and OTHER OTHER
lazyLoadTriggerMethods Specifies which Object's methods trigger a lazy load A method name list separated by commas equals,clone,hashCode,toString
defaultScriptingLanguage Specifies the language used by default for dynamic SQL generation. A type alias or fully qualified class name. org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
defaultEnumTypeHandler Specifies the TypeHandler used by default for Enum. (Since: 3.4.5) A type alias or fully qualified class name. org.apache.ibatis.type.EnumTypeHandler
callSettersOnNulls Specifies if setters or map's put method will be called when a retrieved value is null. It is useful when you rely on Map.keySet() or null value initialization. Note primitives such as (int,boolean,etc.) will not be set to null. true | false false
returnInstanceForEmptyRow MyBatis, by default, returns null when all the columns of a returned row are NULL. When this setting is enabled, MyBatis returns an empty instance instead. Note that it is also applied to nested results (i.e. collectioin and association). Since: 3.4.2 true | false false
logPrefix Specifies the prefix string that MyBatis will add to the logger names. Any String Not set
logImpl Specifies which logging implementation MyBatis should use. If this setting is not present logging implementation will be autodiscovered. SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING Not set
proxyFactory Specifies the proxy tool that MyBatis will use for creating lazy loading capable objects. CGLIB | JAVASSIST JAVASSIST (MyBatis 3.3 or above)
vfsImpl Specifies VFS implementations Fully qualified class names of custom VFS implementation separated by commas. Not set
useActualParamName Allow referencing statement parameters by their actual names declared in the method signature. To use this feature, your project must be compiled in Java 8 with -parameters option. (Since: 3.4.1) true | false true
configurationFactory Specifies the class that provides an instance of Configuration. The returned Configuration instance is used to load lazy properties of deserialized objects. This class must have a method with a signature static Configuration getConfiguration(). (Since: 3.2.3) A type alias or fully qualified class name. Not set
shrinkWhitespacesInSql Removes extra whitespace characters from the SQL. Note that this also affects literal strings in SQL. (Since 3.5.5) true | false false

xml配置

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods"
    value="equals,clone,hashCode,toString"/>
</settings>

后续源码分析时,会针对性进行分析

typeAliases

类型别名,针对Java类的别名设置,简化调用

指定类,并进行别名设置

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
  <typeAlias alias="Comment" type="domain.blog.Comment"/>
  <typeAlias alias="Post" type="domain.blog.Post"/>
  <typeAlias alias="Section" type="domain.blog.Section"/>
  <typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

指定包名,配合注解进行别名设置

<typeAliases>
  <package name="domain.blog"/>
</typeAliases>
@Alias("author")
public class Author {
    ...
}

如果包下未指定注解,则取类名首字母小写(Author --> author)

内建别名

mybatis内建了一批基础别名设置,原始类型前面添加了_下划线,其余均是首字母小写形式

Alias Mapped Type
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

typeHandlers

mybatis进行对象与数据库记录映射需要进行类型转换,主要发挥作用的就是TypeHandler,TypeHandler定义了解析接口,具体实现交由各个映射关系的解析器去实现,用户可自定义类型处理器来处理mybatis没有实现的类型转换

Type Handler Java Types JDBC Types
BooleanTypeHandler java.lang.Boolean, boolean Any compatible BOOLEAN
ByteTypeHandler java.lang.Byte, byte Any compatible NUMERIC or BYTE
ShortTypeHandler java.lang.Short, short Any compatible NUMERIC or SMALLINT
IntegerTypeHandler java.lang.Integer, int Any compatible NUMERIC or INTEGER
LongTypeHandler java.lang.Long, long Any compatible NUMERIC or BIGINT
FloatTypeHandler java.lang.Float, float Any compatible NUMERIC or FLOAT
DoubleTypeHandler java.lang.Double, double Any compatible NUMERIC or DOUBLE
BigDecimalTypeHandler java.math.BigDecimal Any compatible NUMERIC or DECIMAL
StringTypeHandler java.lang.String CHAR, VARCHAR
ClobReaderTypeHandler java.io.Reader -
ClobTypeHandler java.lang.String CLOB, LONGVARCHAR
NStringTypeHandler java.lang.String NVARCHAR, NCHAR
NClobTypeHandler java.lang.String NCLOB
BlobInputStreamTypeHandler java.io.InputStream -
ByteArrayTypeHandler byte[] Any compatible byte stream type
BlobTypeHandler byte[] BLOB, LONGVARBINARY
DateTypeHandler java.util.Date TIMESTAMP
DateOnlyTypeHandler java.util.Date DATE
TimeOnlyTypeHandler java.util.Date TIME
SqlTimestampTypeHandler java.sql.Timestamp TIMESTAMP
SqlDateTypeHandler java.sql.Date DATE
SqlTimeTypeHandler java.sql.Time TIME
ObjectTypeHandler Any OTHER, or unspecified
EnumTypeHandler Enumeration Type VARCHAR any string compatible type, as the code is stored (not index).
EnumOrdinalTypeHandler Enumeration Type Any compatible NUMERIC or DOUBLE, as the position is stored (not the code itself).
SqlxmlTypeHandler java.lang.String SQLXML
InstantTypeHandler java.time.Instant TIMESTAMP
LocalDateTimeTypeHandler java.time.LocalDateTime TIMESTAMP
LocalDateTypeHandler java.time.LocalDate DATE
LocalTimeTypeHandler java.time.LocalTime TIME
OffsetDateTimeTypeHandler java.time.OffsetDateTime TIMESTAMP
OffsetTimeTypeHandler java.time.OffsetTime TIME
ZonedDateTimeTypeHandler java.time.ZonedDateTime TIMESTAMP
YearTypeHandler java.time.Year INTEGER
MonthTypeHandler java.time.Month INTEGER
YearMonthTypeHandler java.time.YearMonth VARCHAR or LONGVARCHAR
JapaneseDateTypeHandler java.time.chrono.JapaneseDate DATE

自定义

实现

// ExampleTypeHandler.java
@MappedJdbcTypes(value = JdbcType.VARCHAR, includeNullJdbcType = true)
@MappedTypes(String.class)
public class ExampleTypeHandler extends BaseTypeHandler<String> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i,
    String parameter, JdbcType jdbcType) throws SQLException {
    ps.setString(i, parameter);
  }

  @Override
  public String getNullableResult(ResultSet rs, String columnName)
    throws SQLException {
    return rs.getString(columnName);
  }

  @Override
  public String getNullableResult(ResultSet rs, int columnIndex)
    throws SQLException {
    return rs.getString(columnIndex);
  }

  @Override
  public String getNullableResult(CallableStatement cs, int columnIndex)
    throws SQLException {
    return cs.getString(columnIndex);
  }
}

加载自定义实现

<!-- mybatis-config.xml -->
<typeHandlers>
  <typeHandler handler="org.mybatis.example.ExampleTypeHandler" javaType="String" jdbcType="VARCHAR"/>
</typeHandlers>

自定义的类型处理器如果存在内建支持的类型,则会覆盖mybatis默认实现,这一点请在实现的时候进行注意,还有一点值得注意的地方是在配置文件中指定了转换类型的时候,加载时会忽略注解的对应指定类型

也可以执行包名配合注解形式加载类型处理器

<!-- mybatis-config.xml -->
<typeHandlers>
  <package name="org.mybatis.example"/>
</typeHandlers>

objectFactory

对象生成工厂类,主要用来实例化类

plugins

mybatis拦截器-允许用户对执行过程添加自己的实现逻辑,能够处理的地方只有指定几个地方:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

在自定义插件的时候一定要熟悉每一个组件的含义,以及逻辑的影响点,不然可能导致不可预期的副作用

// ExamplePlugin.java
@Intercepts({@Signature(
  type= Executor.class,
  method = "update",
  args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
  private Properties properties = new Properties();

  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    // implement pre-processing if needed
    Object returnObject = invocation.proceed();
    // implement post-processing if needed
    return returnObject;
  }

  @Override
  public void setProperties(Properties properties) {
    this.properties = properties;
  }
}
<!-- mybatis-config.xml -->
<plugins>
  <plugin interceptor="org.mybatis.example.ExamplePlugin">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>

environments

支持不同环境设置不同的数据库连接,每个环境只允许设置一个,方便区分环境配置

<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <dataSource type="POOLED">
      <property name="driver" value="${driver}"/>
      <property name="url" value="${url}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
    </dataSource>
  </environment>
</environments>

关键配置

transactionManager-事务管理器

  • JDBC 默认配置
  • MANAGED 集成第三方事务管理器,包含了关闭连接的默认设置,可以设置关闭
<transactionManager type="MANAGED">
  <property name="closeConnection" value="false"/>
</transactionManager>

dataSource- 数据源

  • UNPOOLED 无池化数据源
  • POOLED 池化数据源
  • JNDI

自定义数据源实现

public interface DataSourceFactory {
  void setProperties(Properties props);
  DataSource getDataSource();
}

databaseIdProvider

提供数据库厂商ID的支持(DatabaseMetaData#getDatabaseProductName())

<databaseIdProvider type="DB_VENDOR">
  <property name="SQL Server" value="sqlserver"/>
  <property name="DB2" value="db2"/>
  <property name="Oracle" value="oracle" />
</databaseIdProvider>

自定义

public interface DatabaseIdProvider {
  default void setProperties(Properties p) { // Since 3.5.2, changed to default method
    // NOP
  }
  String getDatabaseId(DataSource dataSource) throws SQLException;
}

mappers

SQL的映射配置的加载方式配置

<!-- Using classpath relative resources -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

<!-- Using url fully qualified paths -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>

<!-- Using mapper interface classes -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

<!-- Register all interfaces in a package as mappers -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

映射配置

初始化配置中配置好了加载SQL映射文件或者包名的路径后,mybatis会读取配置文件或者解析类构件SQL映射关系

  • cache – Configuration of the cache for a given namespace.
  • cache-ref – Reference to a cache configuration from another namespace.
  • resultMap – The most complicated and powerful element that describes how to load your objects from the database result sets.
  • parameterMap – Deprecated! Old-school way to map parameters. Inline parameters are preferred and this element may be removed in the future. Not documented here.
  • sql – A reusable chunk of SQL that can be referenced by other statements.
  • insert – A mapped INSERT statement.
  • update – A mapped UPDATE statement.
  • delete – A mapped DELETE statement.
  • select – A mapped SELECT statement.

select

查询SQL

<select id="selectPerson" parameterType="int" resultType="hashmap">
  SELECT * FROM PERSON WHERE ID = #{id}
</select>

=

// Similar JDBC code, NOT MyBatis…
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);

包含属性

Attribute Description
id A unique identifier in this namespace that can be used to reference this statement.
parameterType The fully qualified class name or alias for the parameter that will be passed into this statement. This attribute is optional because MyBatis can calculate the TypeHandler to use out of the actual parameter passed to the statement. Default is unset.
parameterMap This is a deprecated approach to referencing an external parameterMap. Use inline parameter mappings and the parameterType attribute.
resultType The fully qualified class name or alias for the expected type that will be returned from this statement. Note that in the case of collections, this should be the type that the collection contains, not the type of the collection itself. Use resultType OR resultMap, not both.
resultMap A named reference to an external resultMap. Result maps are the most powerful feature of MyBatis, and with a good understanding of them, many difficult mapping cases can be solved. Use resultMap OR resultType, not both.
flushCache Setting this to true will cause the local and 2nd level caches to be flushed whenever this statement is called. Default: false for select statements.
useCache Setting this to true will cause the results of this statement to be cached in 2nd level cache. Default: true for select statements.
timeout This sets the number of seconds the driver will wait for the database to return from a request, before throwing an exception. Default is unset (driver dependent).
fetchSize This is a driver hint that will attempt to cause the driver to return results in batches of rows numbering in size equal to this setting. Default is unset (driver dependent).
statementType Any one of STATEMENT, PREPARED or CALLABLE. This causes MyBatis to use Statement, PreparedStatement or CallableStatement respectively. Default: PREPARED.
resultSetType Any one of FORWARD_ONLY SCROLL_SENSITIVE SCROLL_INSENSITIVE DEFAULT(same as unset). Default is unset (driver dependent).
databaseId In case there is a configured databaseIdProvider, MyBatis will load all statements with no databaseId attribute or with a databaseId that matches the current one. If case the same statement if found with and without the databaseId the latter will be discarded.
resultOrdered This is only applicable for nested result select statements: If this is true, it is assumed that nested results are contained or grouped together such that when a new main result row is returned, no references to a previous result row will occur anymore. This allows nested results to be filled much more memory friendly. Default: false.
resultSets This is only applicable for multiple result sets. It lists the result sets that will be returned by the statement and gives a name to each one. Names are separated by commas.

insert, update and delete

更新SQL

Attribute Description
id A unique identifier in this namespace that can be used to reference this statement.
parameterType The fully qualified class name or alias for the parameter that will be passed into this statement. This attribute is optional because MyBatis can calculate the TypeHandler to use out of the actual parameter passed to the statement. Default is unset.
parameterMap This is a deprecated approach to referencing an external parameterMap. Use inline parameter mappings and the parameterType attribute.
flushCache Setting this to true will cause the 2nd level and local caches to be flushed whenever this statement is called. Default: true for insert, update and delete statements.
timeout This sets the maximum number of seconds the driver will wait for the database to return from a request, before throwing an exception. Default is unset (driver dependent).
statementType Any one of STATEMENT, PREPARED or CALLABLE. This causes MyBatis to use Statement, PreparedStatement or CallableStatement respectively. Default: PREPARED.
useGeneratedKeys (insert and update only) This tells MyBatis to use the JDBC getGeneratedKeys method to retrieve keys generated internally by the database (e.g. auto increment fields in RDBMS like MySQL or SQL Server). Default: false.
keyProperty (insert and update only) Identifies a property into which MyBatis will set the key value returned by getGeneratedKeys, or by a selectKey child element of the insert statement. Default: unset. Can be a comma separated list of property names if multiple generated columns are expected.
keyColumn (insert and update only) Sets the name of the column in the table with a generated key. This is only required in certain databases (like PostgreSQL) when the key column is not the first column in the table. Can be a comma separated list of columns names if multiple generated columns are expected.
databaseId In case there is a configured databaseIdProvider, MyBatis will load all statements with no databaseId attribute or with a databaseId that matches the current one. If case the same statement if found with and without the databaseId the latter will be discarded.

案例

<!-- 自增主键 -->
<insert id="insertAuthor" useGeneratedKeys="true"
    keyProperty="id">
  insert into Author (username,password,email,bio)
  values (#{username},#{password},#{email},#{bio})
</insert>

<!-- 批量插入 -->
<insert id="insertAuthor" useGeneratedKeys="true"
    keyProperty="id">
  insert into Author (username, password, email, bio) values
  <foreach item="item" collection="list" separator=",">
    (#{item.username}, #{item.password}, #{item.email}, #{item.bio})
  </foreach>
</insert>

<insert id="insertAuthor">
  <!-- 支持自定义主键生成 -->
  <selectKey keyProperty="id" resultType="int" order="BEFORE">
    select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
  </selectKey>
  insert into Author
    (id, username, password, email,bio, favourite_section)
  values
    (#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR})
</insert>

selectKey

Attribute Description
keyProperty The target property where the result of the selectKey statement should be set. Can be a comma separated list of property names if multiple generated columns are expected.
keyColumn The column name(s) in the returned result set that match the properties. Can be a comma separated list of column names if multiple generated columns are expected.
resultType The type of the result. MyBatis can usually figure this out, but it doesn't hurt to add it to be sure. MyBatis allows any simple type to be used as the key, including Strings. If you are expecting multiple generated columns, then you can use an Object that contains the expected properties, or a Map.
order This can be set to BEFORE or AFTER. If set to BEFORE, then it will select the key first, set the keyProperty and then execute the insert statement. If set to AFTER, it runs the insert statement and then the selectKey statement – which is common with databases like Oracle that may have embedded sequence calls inside of insert statements.
statementType Same as above, MyBatis supports STATEMENT, PREPARED and CALLABLE statement types that map to Statement, PreparedStatement and CallableStatement respectively.

sql

可复用SQL

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>


<select id="selectUsers" resultType="map">
  select
      <!-- 引用SQL节点 -->
    <include refid="userColumns"><property name="alias" value="t1"/></include>,
    <include refid="userColumns"><property name="alias" value="t2"/></include>
  from some_table t1
    cross join some_table t2
</select>

Parameters

SQL具体的参数变量

#{property,javaType=int,jdbcType=NUMERIC}
#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}
#{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}
#{middleInitial, mode=OUT, jdbcType=STRUCT, jdbcTypeName=MY_TYPE, resultMap=departmentResultMap}

String Substitution

常量替换(美元符号)

${stringSubstitution}

Result Maps

数据库字段与Java对象映射关系

<select id="selectUsers" resultType="com.someapp.model.User">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

<!-- In Config XML file -->
<typeAlias type="com.someapp.model.User" alias="User"/>

<!-- In SQL Mapping XML file -->
<select id="selectUsers" resultType="User">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

<select id="selectUsers" resultType="User">
  select
    user_id             as "id",
    user_name           as "userName",
    hashed_password     as "hashedPassword"
  from some_table
  where id = #{id}
</select>

<resultMap id="userResultMap" type="User">
  <id property="id" column="user_id" />
  <result property="username" column="user_name"/>
  <result property="password" column="hashed_password"/>
</resultMap>

<select id="selectUsers" resultMap="userResultMap">
  select user_id, user_name, hashed_password
  from some_table
  where id = #{id}
</select>

Advanced Result Maps

高级映射配置

<!-- Very Complex Result Map -->
<resultMap id="detailedBlogResultMap" type="Blog">
  <constructor>
    <idArg column="blog_id" javaType="int"/>
  </constructor>
  <result property="title" column="blog_title"/>
  <association property="author" javaType="Author">
    <id property="id" column="author_id"/>
    <result property="username" column="author_username"/>
    <result property="password" column="author_password"/>
    <result property="email" column="author_email"/>
    <result property="bio" column="author_bio"/>
    <result property="favouriteSection" column="author_favourite_section"/>
  </association>
  <collection property="posts" ofType="Post">
    <id property="id" column="post_id"/>
    <result property="subject" column="post_subject"/>
    <association property="author" javaType="Author"/>
    <collection property="comments" ofType="Comment">
      <id property="id" column="comment_id"/>
    </collection>
    <collection property="tags" ofType="Tag" >
      <id property="id" column="tag_id"/>
    </collection>
    <discriminator javaType="int" column="draft">
      <case value="1" resultType="DraftPost"/>
    </discriminator>
  </collection>
</resultMap>

resultMap

子节点

  • constructor - used for injecting results into the constructor of a class upon instantiation

    • idArg - ID argument; flagging results as ID will help improve overall performance
    • arg - a normal result injected into the constructor
  • id – an ID result; flagging results as ID will help improve overall performance

  • result – a normal result injected into a field or JavaBean property

  • association – a complex type association; many results will roll up into this type

    • nested result mappings – associations are resultMaps themselves, or can refer to one
  • collection – a collection of complex types

    • nested result mappings – collections are resultMaps themselves, or can refer to one

discriminator – uses a result value to determine which resultMap to use

  • case – a case is a result map based on some value
    • nested result mappings – a case is also a result map itself, and thus can contain many of these same elements, or it can refer to an external resultMap.

属性

Attribute Description
id A unique identifier in this namespace that can be used to reference this result map.
type A fully qualified Java class name, or a type alias (see the table above for the list of built-in type aliases).
autoMapping If present, MyBatis will enable or disable the automapping for this ResultMap. This attribute overrides the global autoMappingBehavior. Default: unset.

id & result

Attribute Description
property The field or property to map the column result to. If a matching JavaBeans property exists for the given name, then that will be used. Otherwise, MyBatis will look for a field of the given name. In both cases you can use complex property navigation using the usual dot notation. For example, you can map to something simple like: username, or to something more complicated like: address.street.number.
column The column name from the database, or the aliased column label. This is the same string that would normally be passed to resultSet.getString(columnName).
javaType A fully qualified Java class name, or a type alias (see the table above for the list of built-in type aliases). MyBatis can usually figure out the type if you're mapping to a JavaBean. However, if you are mapping to a HashMap, then you should specify the javaType explicitly to ensure the desired behaviour.
jdbcType The JDBC Type from the list of supported types that follows this table. The JDBC type is only required for nullable columns upon insert, update or delete. This is a JDBC requirement, not a MyBatis one. So even if you were coding JDBC directly, you'd need to specify this type – but only for nullable values.
typeHandler We discussed default type handlers previously in this documentation. Using this property you can override the default type handler on a mapping-by-mapping basis. The value is either a fully qualified class name of a TypeHandler implementation, or a type alias.

Supported JDBC Types

BIT FLOAT CHAR TIMESTAMP OTHER UNDEFINED
TINYINT REAL VARCHAR BINARY BLOB NVARCHAR
SMALLINT DOUBLE LONGVARCHAR VARBINARY CLOB NCHAR
INTEGER NUMERIC DATE LONGVARBINARY BOOLEAN NCLOB
BIGINT DECIMAL TIME NULL CURSOR ARRAY

constructor

构造器传参实例化对象,无name属性必须按照顺序寻找构造器,有name属性必须指定编译选项(-parameters)

<constructor>
   <idArg column="id" javaType="int" name="id" />
   <arg column="age" javaType="_int" name="age" />
   <arg column="username" javaType="String" name="username" />
</constructor>

属性

Attribute Description
column The column name from the database, or the aliased column label. This is the same string that would normally be passed to resultSet.getString(columnName).
javaType A fully qualified Java class name, or a type alias (see the table above for the list of built-in type aliases). MyBatis can usually figure out the type if you're mapping to a JavaBean. However, if you are mapping to a HashMap, then you should specify the javaType explicitly to ensure the desired behaviour.
jdbcType The JDBC Type from the list of supported types that follows this table. The JDBC type is only required for nullable columns upon insert, update or delete. This is a JDBC requirement, not an MyBatis one. So even if you were coding JDBC directly, you'd need to specify this type – but only for nullable values.
typeHandler We discussed default type handlers previously in this documentation. Using this property you can override the default type handler on a mapping-by-mapping basis. The value is either a fully qualified class name of a TypeHandler implementation, or a type alias.
select The ID of another mapped statement that will load the complex type required by this property mapping. The values retrieved from columns specified in the column attribute will be passed to the target select statement as parameters. See the Association element for more.
resultMap This is the ID of a ResultMap that can map the nested results of this argument into an appropriate object graph. This is an alternative to using a call to another select statement. It allows you to join multiple tables together into a single ResultSet. Such a ResultSet will contain duplicated, repeating groups of data that needs to be decomposed and mapped properly to a nested object graph. To facilitate this, MyBatis lets you "chain" result maps together, to deal with the nested results. See the Association element below for more.
name The name of the constructor parameter. Specifying name allows you to write arg elements in any order. See the above explanation. Since 3.4.3.

association

1:1关联记录 "has-one"

Attribute Description
property The field or property to map the column result to. If a matching JavaBeans property exists for the given name, then that will be used. Otherwise, MyBatis will look for a field of the given name. In both cases you can use complex property navigation using the usual dot notation. For example, you can map to something simple like: username, or to something more complicated like: address.street.number.
javaType A fully qualified Java class name, or a type alias (see the table above for the list of built- in type aliases). MyBatis can usually figure out the type if you're mapping to a JavaBean. However, if you are mapping to a HashMap, then you should specify the javaType explicitly to ensure the desired behaviour.
jdbcType The JDBC Type from the list of supported types that follows this table. The JDBC type is only required for nullable columns upon insert, update or delete. This is a JDBC requirement, not an MyBatis one. So even if you were coding JDBC directly, you'd need to specify this type – but only for nullable values.
typeHandler We discussed default type handlers previously in this documentation. Using this property you can override the default type handler on a mapping-by-mapping basis. The value is either a fully qualified class name of a TypeHandler implementation, or a type alias.
Nested Select for Association

级联查询

Attribute Description
column The column name from the database, or the aliased column label that holds the value that will be passed to the nested statement as an input parameter. This is the same string that would normally be passed to resultSet.getString(columnName). Note: To deal with composite keys, you can specify multiple column names to pass to the nested select statement by using the syntax column="{prop1=col1,prop2=col2}". This will cause prop1 and prop2 to be set against the parameter object for the target nested select statement.
select The ID of another mapped statement that will load the complex type required by this property mapping. The values retrieved from columns specified in the column attribute will be passed to the target select statement as parameters. A detailed example follows this table. Note: To deal with composite keys, you can specify multiple column names to pass to the nested select statement by using the syntax column="{prop1=col1,prop2=col2}". This will cause prop1 and prop2 to be set against the parameter object for the target nested select statement.
fetchType Optional. Valid values are lazy and eager. If present, it supersedes the global configuration parameter lazyLoadingEnabled for this mapping.
<resultMap id="blogResult" type="Blog">
  <association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  SELECT * FROM BLOG WHERE ID = #{id}
</select>

<select id="selectAuthor" resultType="Author">
  SELECT * FROM AUTHOR WHERE ID = #{id}
</select>

上述方式会导致经典的N+1问题

Nested Results for Association

关联查询

Attribute Description
resultMap This is the ID of a ResultMap that can map the nested results of this association into an appropriate object graph. This is an alternative to using a call to another select statement. It allows you to join multiple tables together into a single ResultSet. Such a ResultSet will contain duplicated, repeating groups of data that needs to be decomposed and mapped properly to a nested object graph. To facilitate this, MyBatis lets you "chain" result maps together, to deal with the nested results. An example will be far easier to follow, and one follows this table.
columnPrefix When joining multiple tables, you would have to use column alias to avoid duplicated column names in the ResultSet. Specifying columnPrefix allows you to map such columns to an external resultMap. Please see the example explained later in this section.
notNullColumn By default a child object is created only if at least one of the columns mapped to the child's properties is non null. With this attribute you can change this behaviour by specifiying which columns must have a value so MyBatis will create a child object only if any of those columns is not null. Multiple column names can be specified using a comma as a separator. Default value: unset.
autoMapping If present, MyBatis will enable or disable automapping when mapping the result to this property. This attribute overrides the global autoMappingBehavior. Note that it has no effect on an external resultMap, so it is pointless to use it with select or resultMap attribute. Default value: unset.

配置

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author" resultMap="authorResult" />
</resultMap>

<resultMap id="authorResult" type="Author">
  <id property="id" column="author_id"/>
  <result property="username" column="author_username"/>
  <result property="password" column="author_password"/>
  <result property="email" column="author_email"/>
  <result property="bio" column="author_bio"/>
</resultMap>


<!-- 或者写在一起 -->
<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author" javaType="Author">
    <id property="id" column="author_id"/>
    <result property="username" column="author_username"/>
    <result property="password" column="author_password"/>
    <result property="email" column="author_email"/>
    <result property="bio" column="author_bio"/>
  </association>
</resultMap>

<!-- 复用 -->
<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author" resultMap="authorResult" />
  <!-- 复用authorResult并添加前缀映射 -->
  <association property="coAuthor" resultMap="authorResult" columnPrefix="co_" />
</resultMap>

sql

<select id="selectBlog" resultMap="blogResult">
  select
    B.id            as blog_id,
    B.title         as blog_title,
    B.author_id     as blog_author_id,
    A.id            as author_id,
    A.username      as author_username,
    A.password      as author_password,
    A.email         as author_email,
    A.bio           as author_bio
  from Blog B left outer join Author A on B.author_id = A.id
  where B.id = #{id}
</select>
Multiple ResultSets for Association

返回多个结果集

Attribute Description
column When using multiple resultset this attribute specifies the columns (separated by commas) that will be correlated with the foreignColumn to identify the parent and the child of a relationship.
foreignColumn Identifies the name of the columns that contains the foreign keys which values will be matched against the values of the columns specified in the column attibute of the parent type.
resultSet Identifies the name of the result set where this complex type will be loaded from.
<select id="selectBlog" resultSets="blogs,authors" resultMap="blogResult" statementType="CALLABLE">
  {call getBlogsAndAuthors(#{id,jdbcType=INTEGER,mode=IN})}
</select>

<resultMap id="blogResult" type="Blog">
  <id property="id" column="id" />
  <result property="title" column="title"/>
  <association property="author" javaType="Author" resultSet="authors" column="author_id" foreignColumn="id">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="password" column="password"/>
    <result property="email" column="email"/>
    <result property="bio" column="bio"/>
  </association>
</resultMap>

collection

一对多映射( "has many")

<collection property="posts" ofType="domain.blog.Post">
  <id property="id" column="post_id"/>
  <result property="subject" column="post_subject"/>
  <result property="body" column="post_body"/>
</collection>
Nested Select for Collection

嵌套级联查询

<resultMap id="blogResult" type="Blog">
  <collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  SELECT * FROM BLOG WHERE ID = #{id}
</select>

<select id="selectPostsForBlog" resultType="Post">
  SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>
Nested Results for Collection

关联查询

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <collection property="posts" ofType="Post">
    <id property="id" column="post_id"/>
    <result property="subject" column="post_subject"/>
    <result property="body" column="post_body"/>
  </collection>
</resultMap>
Multiple ResultSets for Collection

多返回值

<resultMap id="blogResult" type="Blog">
  <id property="id" column="id" />
  <result property="title" column="title"/>
  <collection property="posts" ofType="Post" resultSet="posts" column="id" foreignColumn="blog_id">
    <id property="id" column="id"/>
    <result property="subject" column="subject"/>
    <result property="body" column="body"/>
  </collection>
</resultMap>

discriminator

分类器--按指定字段区分查询字段并进行不同继承对象的封装

<resultMap id="vehicleResult" type="Vehicle">
  <id property="id" column="id" />
  <result property="vin" column="vin"/>
  <result property="year" column="year"/>
  <result property="make" column="make"/>
  <result property="model" column="model"/>
  <result property="color" column="color"/>
  <discriminator javaType="int" column="vehicle_type">
    <case value="1" resultMap="carResult"/>
    <case value="2" resultMap="truckResult"/>
    <case value="3" resultMap="vanResult"/>
    <case value="4" resultMap="suvResult"/>
  </discriminator>
</resultMap>

<!-- extends 可加载父记录字段 -->
<resultMap id="carResult" type="Car" extends="vehicleResult">
  <result property="doorCount" column="door_count" />
</resultMap>

Auto-mapping

未手动配置映射关系的,将按照数据库下划线解析成Java驼峰式名称进行自动匹配映射(mapUnderscoreToCamelCase)

匹配模式,默认PARTIAL,FULL有风险,不建议使用,最好手动指定

  • NONE - disables auto-mapping. Only manually mapped properties will be set.
  • PARTIAL - will auto-map results except those that have nested result mappings defined inside (joins).
  • FULL - auto-maps everything.

可针对单一resultMap设置关闭自动映射

<resultMap id="userResultMap" type="User" autoMapping="false">
  <result property="password" column="hashed_password"/>
</resultMap>

cache

查询缓存,mabatis支持 一级缓存(sesson级别,默认开启),以及二级缓存

<!-- 开启二级缓存 -->
<cache/>
  • All results from select statements in the mapped statement file will be cached. 查询都将被缓存
  • All insert, update and delete statements in the mapped statement file will flush the cache. 更新将删除缓存
  • The cache will use a Least Recently Used (LRU) algorithm for eviction. 使用最少使用策略剔除缓存
  • The cache will not flush on any sort of time based schedule (i.e. no Flush Interval). 没有定时任务会清除缓存
  • The cache will store 1024 references to lists or objects (whatever the query method returns). 最多维持1024个结果
  • The cache will be treated as a read/write cache, meaning objects retrieved are not shared and can be safely modified by the caller, without interfering with other potential modifications by other callers or threads. 缓存可读写

缓存绑定对应配置的命名空间上,仅支持xml配置,如果需要进行Java注解支持,需标注 @CacheNamespaceRef

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

清除算法支持

  • LRU – Least Recently Used: Removes objects that haven't been used for the longst period of time.
  • FIFO – First In First Out: Removes objects in the order that they entered the cache.
  • SOFT – Soft Reference: Removes objects based on the garbage collector state and the rules of Soft References.
  • WEAK – Weak Reference: More aggressively removes objects based on the garbage collector state and rules of Weak References.

Using a Custom Cache

<cache type="com.domain.something.MyCustomCache"/>

实现Cache接口,并加载执行缓存实现

public interface Cache {
  String getId();
  int getSize();
  void putObject(Object key, Object value);
  Object getObject(Object key);
  boolean hasKey(Object key);
  Object removeObject(Object key);
  void clear();
}

cache-ref

复用其他命名空间的缓存配置

Dynamic SQL

mybatis强大的动态SQL支持,通过动态标签进行支持

if

条件选择,不支持elseif表达式

<if test="ognl express return boolean">
  SQL expression
</if>

choose, when, otherwise

条件选择,选择满足条件之一进行执行 类似 Java中的 swtich表达式

<choose>
  <when test="condtion1">
    SQL 1
  </when>
  <when test="condition2">
    SQL 2
  </when>
  <!--  else 其他情况默认走 otherwise -->
  <otherwise>
    SQL 3
  </otherwise>
</choose>

trim, where, set

Where 动态添加去除多余 and | or 关键字

<where>
  dynamic sql
</where>

等同于,不过 trim 更加强大

<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

set 更新语句 动态去除多余 , 号

<set>
    condition express
</set>

等同于

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

foreach

遍历列表迭代器,支持列表( index:索引, item : value )和字典 (index : key , item : value)

<foreach item="item" index="index" collection="list" open="(" separator="," close=")">
  #{item}
</foreach>

script

支持Java注解内写动态SQL脚本

bind

支持变量绑定

<bind name=" variable name " value=" ognl expression " />

Multi-db vendor support

支持不同db的变量传递 (_databaseId)

Pluggable Scripting Languages For Dynamic SQL

自定义自己的解析器

public interface LanguageDriver {
  ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
  SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
  SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}

配置,修改默认脚本语言,会替换mybatis默认的脚本解析支持

<typeAliases>
  <typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/>
</typeAliases>
<settings>
  <setting name="defaultScriptingLanguage" value="myLanguage"/>
</settings>

可指定语句进行支持

<select id="selectBlog" lang="myLanguage">
  SELECT * FROM BLOG
</select>

或者Java注解支持

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