mybatis學習

1.1mybatis下載

mybaits 的代碼由github.com 管理,
地址:https://github.com/mybatis/mybatis-3/releases

mybatis文件包

mybatis-3.4.6.jar----mybatis 的核心包
lib----mybatis 的依賴包
mybatis-3.4.6.pdf----mybatis 使用手冊

1.2創(chuàng)建mysql 數(shù)據(jù)庫

1.3Mybatis 入門程序

1.3.1需求

實現(xiàn)以下功能:
根據(jù)用戶id 查詢一個用戶信息
根據(jù)用戶名稱模糊查詢用戶信息列表
添加用戶
更新用戶
刪除用戶

1.3.2 第一步:創(chuàng)建java 工程

使用eclipse 創(chuàng)建java 工程,jdk 使用jdk1.8.0_144

1.3.3 第二步:加入jar 包

加入mybatis 核心包、依賴包、數(shù)據(jù)驅(qū)動包。

image.png

1.3.4 第三步:log4j.properties

在classpath 下創(chuàng)建log4j.properties如下:

# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

mybatis 默認使用log4j 作為輸出日志信息。

1.3.5 第四步:SqlMapConfig.xml

在classpath 下創(chuàng)建SqlMapConfig.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>
    <!-- 和spring整合后environments配置將廢除 -->
    <environments default="development">
        <environment id="development">
            <!-- 使用jdbc事務管理 -->
            <transactionManager type="JDBC" />
            <!-- 數(shù)據(jù)庫連接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url"
                    value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8" />
                <property name="username" value="root" />
                <property name="password" value="mysql" />
            </dataSource>
        </environment>
    </environments>
</configuration>

SqlMapConfig.xml 是mybatis 核心配置文件,上邊文件的配置內(nèi)容為數(shù)據(jù)源、事務管理。

1.3.6 第五步:po 類

Po 類作為mybatis 進行sql 映射使用,po 類通常與數(shù)據(jù)庫表對應,User.java 如下:

package com.ghw.po;

import java.util.Date;

public class User {

    private int id;
    private String username;// 用戶姓名
    private String sex;// 性別
    private Date birthday;// 生日
    private String address;// 地址

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

}

1.3.7 第六步:程序編寫

1.3.7.1查詢

1.3.7.1.1 映射文件:

在classpath 下的sqlmap 目錄下創(chuàng)建sql 映射文件Users.xml:

<?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="test">
</mapper>

namespace :命名空間,用于隔離sql 語句,后面會講另一層非常重要的作用。

在Users.xml 中添加:

<mapper namespace="test">
    <!-- 根據(jù)id獲取用戶信息 -->
    <select id="findUserById" parameterType="int"
        resultType="com.ghw.po.User">
        select * from user where id = #{id}
    </select>
    <!-- 自定義條件查詢用戶列表 -->
    <select id="findUserByUsername" parameterType="java.lang.String"
        resultType="cn.itcast.mybatis.po.User">
        select * from user where username like '%${value}%'
    </select>
</mapper>

parameterType:定義輸入到sql 中的映射類型,#{id}表示使用preparedstatement 設
置占位符號并將輸入變量id 傳到sql。
resultType:定義結(jié)果映射類型。

1.3.7.1.2 加載映射文件

mybatis 框架需要加載映射文件,將Users.xml 添加在SqlMapConfig.xml,如下:

<mappers>
        <mapper resource="sqlmap/User.xml" />
</mappers>
1.3.7.1.3 測試程序:
package com.ghw.fitst;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.log4j.Logger;
import org.junit.Test;

import com.ghw.po.User;

public class Mybatis_Test {
    static Logger logger = Logger.getLogger(Mybatis_Test.class);

    @Test
    public void findUserByIdTest() throws IOException {
        // 讀取mybatis配置文件
        String resource = "SqlMapConfig.xml";
        // 得到配置文件流
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 創(chuàng)建會話工廠,傳入mybatis配置文件流
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 通過工廠得到SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 通過SqlSession操作數(shù)據(jù)庫
        // 第一個參數(shù):映射文件中statement的id,等于=namespace+"."+statement的id
        // 第二個參數(shù):指定和映射文件中所匹配的parameterType類型的參數(shù)
        // sqlSession.selectOne結(jié)果 是與映射文件中所匹配的resultType類型的對象
        // selectOne查詢出一條記錄
        User user = sqlSession.selectOne("test.findUserById", 1);
        // 輸出查詢到的結(jié)果
        logger.info(user);
    }
    @Test
    public void findUserByNameTest() throws IOException {
        String resource = "SqlMapConfig.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        List<User> list = sqlSession.selectList("test.findUserByusername", "小明");
        logger.info(list);
    }
}
1.3.7.1.4 #{}和${}

#{}表示一個占位符號,通過#{}可以實現(xiàn)preparedStatement向占位符中設置值,自動進行java類型和jdbc類型轉(zhuǎn)換,#{}可以有效防止sql注入。#{}可以接收簡單類型值或pojo屬性值。
如果parameterType傳輸單個簡單類型值,#{}括號中可以是value或其它名稱。
${}表示拼接sql串,通過${}可以將parameterType傳入的內(nèi)容拼接在sql中且不進行jdbc類型轉(zhuǎn)換, ${}可以接收簡單類型值或pojo屬性值,如果parameterType傳輸單個簡單類型值,${}括號中只能是value。可能引起sql注入

1.3.7.1.5 parameterType 和resultType

parameterType:指定輸入?yún)?shù)類型,mybatis 通過ognl 從輸入對象中獲取參數(shù)值拼接在sql中。
resultType:指定輸出結(jié)果類型,mybatis 將sql 查詢結(jié)果的一行記錄數(shù)據(jù)映射為resultType指定類型的對象。

1.3.7.1.6 selectOne 和selectList

selectOne 查詢一條記錄,如果使用selectOne 查詢多條記錄則拋出異常:
org.apache.ibatis.exceptions.TooManyResultsException: Expected one result(or null) to be returned by selectOne(), but found: 3
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultS
qlSession.java:70)
selectList 可以查詢一條或多條記錄。

1.3.7.2添加

1.3.7.2.1 映射文件:

在SqlMapConfig.xml 中添加:

<!-- 添加用戶 -->
    <insert id="insertUser" parameterType="com.ghw.po.User">
        insert into user(id,username,birthday,sex,address) 
        values(#{id},#{username},#{birthday},#{sex},#{address})
</insert>

1.3.7.2.2 測試程序:

// 添加用戶
    public void insertUser() {
        User user1 = new User();
        user1.setUsername("李書豪");
        user1.setBirthday(new Date());
        user1.setSex("男");
        user1.setAddress("陜西渭南");
                //在@before中已經(jīng)獲取了sqlSession
        sqlSession.insert("test.insertUser", user1);
        //提交事務
        sqlSession.commit();
    }

1.3.7.2.3 mysql 自增主鍵返回

通過修改sql 映射文件,可以將mysql 自增主鍵返回:

<insert id="insertUser" parameterType="com.ghw.po.User">
        <selectKey keyProperty="id" order="AFTER" resultType="Integer">
            select LAST_INSERT_ID()
        </selectKey>
        insert into user(username,birthday,sex,address) 
        values(#{username},#{birthday},#{sex},#{address})
    </insert>

添加selectKey 實現(xiàn)將主鍵返回
keyProperty:返回的主鍵存儲在pojo中的哪個屬性
order:selectKey 的執(zhí)行順序,是相對與insert 語句來說,由于mysql 的自增原理執(zhí)行完insert 語句之后才將主鍵生成,所以這里selectKey 的執(zhí)行順序為after
resultType:返回的主鍵是什么類型
LAST_INSERT_ID():是mysql 的函數(shù),返回auto_increment 自增列新記錄id 值。

1.3.7.2.4 Mysql 使用uuid 實現(xiàn)主鍵

需要增加通過select uuid()得到uuid 值

<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User">
<selectKey resultType="java.lang.String" order="BEFORE"
keyProperty="id">
select uuid()
</selectKey>
insert into user(id,username,birthday,sex,address)
values(#{id},#{username},#{birthday},#{sex},#{address})
</insert>

注意這里使用的order 是“BEFORE”

1.3.7.2.5 Oracle 使用序列生成主鍵

首先自定義一個序列且用于生成主鍵,selectKey 使用如下:

<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User">
<selectKey resultType="java.lang.Integer" order="BEFORE" keyProperty="id">
SELECT 自定義序列.NEXTVAL FROM DUAL
</selectKey>
insert into user(id,username,birthday,sex,address) values(#{id},#{username},#{birthday},#{sex},#{address})
</insert>

注意這里使用的orderBEFORE

1.3.7.3刪除

1.3.7.3.1 映射文件:

<!-- 刪除用戶 -->
    <delete id="deleteUser" parameterType="int">
        delete from user where id = #{id}
</delete>

1.3.7.3.2 測試程序:

// 刪除用戶
    @Test
    public void deleteUser() {
        // 刪除編號32的用戶
        sqlSession.delete("test.deleteUser", 32);
        // 提交事務
        sqlSession.commit();
        logger.info("刪除成功");
}

1.3.7.4修改

1.3.7.4.1 映射文件

<!-- 修改用戶 -->
<!-- 修改用戶 -->
<update id="updateUser" parameterType="com.ghw.po.User">
    update user set
    username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} 
    where id = #{id}
</update>

1.3.7.4.2 測試程序

// 修改用戶
@Test
public void updateUser() {
    User user2 = new User();
    user2.setId(26);
    user2.setBirthday(new Date());
    user2.setSex("男");
    user2.setUsername("李書豪2");
    user2.setAddress("西安郵電大學");
    sqlSession.update("test.updateUser", user2);
    sqlSession.commit();
    logger.info("修改成功");
}

1.3.8 Mybatis 解決jdbc 編程的問題

  1. 數(shù)據(jù)庫鏈接頻繁建立與釋放鏈接,造成系統(tǒng)資源浪費。
    解決:在SqlMapConfig.xml中配置數(shù)據(jù)鏈接池,使用連接池管理數(shù)據(jù)庫鏈接。
  2. sql語句寫在java代碼中,修改的時候要修改源代碼,不利于后期維護升級。
    解決:將sql語句配置在XXXXmapper.xml 文件中與java 代碼分離。
  3. 向sql 語句傳參數(shù)麻煩,因為sql 語句的where 條件不一定,可能多也可能少,占位符需要和參數(shù)一一對應。
    解決:Mybatis自動將java 對象映射至sql語句,通過statement中的parameterType定義輸入?yún)?shù)的類型。
  4. 對結(jié)果集解析麻煩,sql 變化導致解析代碼變化,且解析前需要遍歷,如果能將數(shù)據(jù)庫記錄封裝成pojo 對象解析比較方便。
    解決:Mybatis 自動將sql 執(zhí)行結(jié)果映射至java 對象,通過statement 中的resultType 定義輸出結(jié)果的類型。

1.3.9 與hibernate 不同

  • Mybatis 和hibernate 不同,它不完全是一個ORM 框架,因為MyBatis 需要程序員自己編寫Sql 語句,不過mybatis 可以通過XML 或注解方式靈活配置要運行的sql 語句,并將java對象和sql 語句映射生成最終執(zhí)行的sql,最后將sql 執(zhí)行的結(jié)果再映射生成java 對象。
  • Mybatis 學習門檻低,簡單易學,程序員直接編寫原生態(tài)sql,可嚴格控制sql 執(zhí)行性能,靈活度高,非常適合對關系數(shù)據(jù)模型要求不高的軟件開發(fā),例如互聯(lián)網(wǎng)軟件、企業(yè)運營類軟件等,因為這類軟件需求變化頻繁,一但需求變化要求成果輸出迅速。但是靈活的前提是mybatis 無法做到數(shù)據(jù)庫無關性,如果需要實現(xiàn)支持多種數(shù)據(jù)庫的軟件則需要自定義多套sql映射文件,工作量大。
  • Hibernate 對象/關系映射能力強,數(shù)據(jù)庫無關性好,對于關系模型要求高的軟件(例如需求固定的定制化軟件)如果用hibernate 開發(fā)可以節(jié)省很多代碼,提高效率。但是Hibernate的學習門檻高,要精通門檻更高,而且怎么設計O/R 映射,在性能和對象模型之間如何權衡,以及怎樣用好Hibernate 需要具有很強的經(jīng)驗和能力才行。
  • 總之,按照用戶的需求在有限的資源環(huán)境下只要能做出維護性、擴展性良好的軟件架構都是好架構,所以框架只有適合才是最好。

2 Dao 開發(fā)方法

使用Mybatis 開發(fā)Dao,通常有兩個方法,即原始Dao開發(fā)方法和Mapper接口開發(fā)方法。

2.1 需求

將下邊的功能實現(xiàn)Dao:

  • 根據(jù)用戶id 查詢一個用戶信息
  • 根據(jù)用戶名稱模糊查詢用戶信息列表
  • 添加用戶信息

2.2 SqlSession 的使用范圍

SqlSession 中封裝了對數(shù)據(jù)庫的操作,如:查詢、插入、更新、刪除等。通過SqlSessionFactory 創(chuàng)建SqlSession,而SqlSessionFactory 是通過SqlSessionFactoryBuilder進行創(chuàng)建。

2.2.1 SqlSessionFactoryBuilder

SqlSessionFactoryBuilder 用于創(chuàng)建SqlSessionFacoty,SqlSessionFacoty 一旦創(chuàng)建完成就不需要SqlSessionFactoryBuilder 了,因為SqlSession 是通過SqlSessionFactory 生產(chǎn),所以可以將SqlSessionFactoryBuilder 當成一個工具類使用,最佳使用范圍是方法范圍即方法體內(nèi)局部變量。

2.2.2 SqlSessionFactory

SqlSessionFactory是一個接口,接口中定義了openSession 的不同重載方法SqlSessionFactory的最佳使用范圍是整個應用運行期間,一旦創(chuàng)建后可以重復使用,通常以單例模式管理SqlSessionFactory。

2.2.3 SqlSession

SqlSession 是一個面向用戶的接口, sqlSession 中定義了數(shù)據(jù)庫操作, 默認使用
DefaultSqlSession 實現(xiàn)類。
執(zhí)行過程如下:

  1. 加載數(shù)據(jù)源等配置信息
    Environment environment = configuration.getEnvironment();
  2. 創(chuàng)建數(shù)據(jù)庫鏈接
  3. 創(chuàng)建事務對象
  4. 創(chuàng)建Executor,SqlSession 所有操作都是通過Executor 完成,mybatis 源碼如下:
if (ExecutorType.BATCH == executorType) {
  executor = newBatchExecutor(this, transaction);
} elseif (ExecutorType.REUSE == executorType) {
  executor = new ReuseExecutor(this, transaction);
} else {
  executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
  executor = new CachingExecutor(executor, autoCommit);
}
  1. SqlSession 的實現(xiàn)類即DefaultSqlSession,此對象中對操作數(shù)據(jù)庫實質(zhì)上用的是Executor
    結(jié)論:
    每個線程都應該有它自己的SqlSession 實例。SqlSession 的實例不能共享使用,它也是
    線程不安全的。因此最佳的范圍是請求或方法范圍。絕對不能將SqlSession 實例的引用放在一個類的靜態(tài)字段或?qū)嵗侄沃小?/strong>打開一個SqlSession;使用完畢就要關閉它。通常把這個關閉操作放到finally 塊中以確保每次都能執(zhí)行關閉。如下:
SqlSession session = sqlSessionFactory.openSession();
try {
    // do work
} finally {
    session.close();
}

2.3 原始Dao 開發(fā)方式

原始Dao 開發(fā)方法需要程序員編寫Dao 接口和Dao 實現(xiàn)類。

2.3.1 映射文件

<?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="test">

    <!-- 根據(jù)id獲取用戶信息 -->
    <select id="findUserById" parameterType="int"
        resultType="com.ghw.po.User">
        select * from user where id = #{id}
    </select>

    <!-- 自定義條件查詢用戶列表 -->
    <select id="findUserByusername" parameterType="String"
        resultType="com.ghw.po.User">
        select * from user where username like '%${value}%'
    </select>

    <!-- 添加用戶 -->
    <insert id="insertUser" parameterType="com.ghw.po.User">
        <selectKey keyProperty="id" order="AFTER"
            resultType="Integer">
            select LAST_INSERT_ID()
        </selectKey>
        insert into user(username,birthday,sex,address)
        values(#{username},#{birthday},#{sex},#{address})
    </insert>

    <!-- 刪除用戶 -->
    <delete id="deleteUser" parameterType="int">
        delete from user where id
        = #{id}
    </delete>

    <!-- 修改用戶 -->
    <update id="updateUser" parameterType="com.ghw.po.User">
        update user set
        username=#{username},birthday=#{birthday},sex=#{sex},address=#{address}
        where id = #{id}
    </update>
</mapper>

2.3.2 Dao 接口

package com.ghw.dao;

import com.ghw.po.User;

public interface UserDao {
    // 根據(jù)id查詢用戶
    public User getUserById(int id) throws Exception;

    // 添加用戶
    public void insertUser(User user) throws Exception;

    // 刪除用戶
    public void deleteUser(int id) throws Exception;

    // 修改用戶
    public void updateUser(User user) throws Exception;
}

Dao實現(xiàn)類

package com.ghw.dao;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;

import com.ghw.po.User;

public class UserDaoImp implements UserDao {
    private SqlSessionFactory sqlSessionFactory;

    public UserDaoImp(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }

    // 根據(jù)id查詢用戶
    public User getUserById(int id) throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        User user = sqlSession.selectOne("test.findUserById", id);
        return user;
    }

    // 添加用戶
    public void insertUser(User user) throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        sqlSession.insert("test.insertUser", user);
        sqlSession.commit();
    }

    // 刪除用戶

    public void deleteUser(int id) throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        sqlSession.delete("test.deleteUser", id);
        sqlSession.commit();
    }

    // 修改用戶
    public void updateUser(User user) throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        sqlSession.update("test.updateUser", user);
        sqlSession.commit();
    }

}

Dao測試類

package com.ghw.dao;

import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;
import com.ghw.po.User;

public class UserDaoImpTest {
    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void before() throws IOException {
        // 讀取mybatis配置文件
        String resource = "SqlMapConfig.xml";
        // 得到配置文件流
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 創(chuàng)建會話工廠,傳入mybatis配置文件流
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 通過工廠得到SqlSession
    }

    // 根據(jù)id查詢用戶測試方法
    @Test
    public void testGetUserById() throws Exception {
        UserDao userDao = new UserDaoImp(sqlSessionFactory);
        User user = userDao.getUserById(26);
    }

    // 添加用戶測試方法
    @Test
    public void testinsertUser() throws Exception {
        UserDao userDao = new UserDaoImp(sqlSessionFactory);
        User user1 = new User();
        user1.setUsername("李書豪");
        user1.setBirthday(new Date());
        user1.setSex("男");
        user1.setAddress("陜西渭南");
        userDao.insertUser(user1);
    }

    // 刪除用戶測試方法
    @Test
    public void testdeleteUser() throws Exception {
        UserDao userDao = new UserDaoImp(sqlSessionFactory);
        userDao.deleteUser(29);
    }

    // 修改用戶測試方法
    @Test
    public void testupdateUser() throws Exception {
        UserDao userDao = new UserDaoImp(sqlSessionFactory);
        User user2 = new User();
        user2.setId(26);
        user2.setBirthday(new Date());
        user2.setSex("女");
        user2.setUsername("李書豪1");
        user2.setAddress("西安郵電大學1");
        userDao.updateUser(user2);
    }
}

2.3.3 問題

原始Dao 開發(fā)中存在以下問題:

  • Dao 方法體存在重復代碼:通過SqlSessionFactory 創(chuàng)建SqlSession,調(diào)用SqlSession 的數(shù)據(jù)庫操作方法
  • 調(diào)用sqlSession 的數(shù)據(jù)庫操作方法需要指定statement 的id,這里存在硬編碼,不
    得于開發(fā)維護。

2.4 Mapper 動態(tài)代理方式

2.4.1 實現(xiàn)原理

Mapper 接口開發(fā)方法只需要程序員編寫Mapper 接口(相當于Dao 接口),由Mybatis框架根據(jù)接口定義創(chuàng)建接口的動態(tài)代理對象,代理對象的方法體同上邊Dao 接口實現(xiàn)類方法。
Mapper 接口開發(fā)需要遵循以下規(guī)范:

  1. Mapper.xml 文件中的namespace 與mapper 接口的類路徑相同。
  2. Mapper 接口方法名和Mapper.xml 中定義的每個statement 的id 相同
  3. Mapper 接口方法的輸入?yún)?shù)類型和mapper.xml 中定義的每個sql 的parameterType 的類型相同
  4. Mapper 接口方法的輸出參數(shù)類型和mapper.xml 中定義的每個sql 的resultType 的類型相同

2.4.2 Mapper.xml(映射文件)

定義mapper 映射文件UserMapper.xml(內(nèi)容同Users.xml),需要修改namespace 的值為UserMapper 接口路徑。將UserMapper.xml 放在classpath 下mapper 目錄下。

<?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="test">

    <!-- 根據(jù)id獲取用戶信息 -->
    <select id="findUserById" parameterType="int"
        resultType="com.ghw.po.User">
        select * from user where id = #{id}
    </select>

    <!-- 自定義條件查詢用戶列表 -->
    <select id="findUserByusername" parameterType="String"
        resultType="com.ghw.po.User">
        select * from user where username like '%${value}%'
    </select>

    <!-- 添加用戶 -->
    <insert id="insertUser" parameterType="com.ghw.po.User">
        <selectKey keyProperty="id" order="AFTER"
            resultType="Integer">
            select LAST_INSERT_ID()
        </selectKey>
        insert into user(username,birthday,sex,address)
        values(#{username},#{birthday},#{sex},#{address})
    </insert>

    <!-- 刪除用戶 -->
    <delete id="deleteUser" parameterType="int">
        delete from user where id
        = #{id}
    </delete>

    <!-- 修改用戶 -->
    <update id="updateUser" parameterType="com.ghw.po.User">
        update user set
        username=#{username},birthday=#{birthday},sex=#{sex},address=#{address}
        where id = #{id}
    </update>
</mapper>

2.4.3 Mapper.java(接口文件)

package com.ghw.mapper;

import com.ghw.po.User;

public interface UserMapper {
    // 根據(jù)id查詢用戶
    public User findUserById(int id) throws Exception;

    // 自定義條件查詢用戶列表
    public User findUserByusername(String name) throws Exception;

    // 添加用戶
    public void insertUser(User user) throws Exception;

    // 刪除用戶
    public void deleteUser(int id) throws Exception;

    // 修改用戶
    public void updateUser(User user) throws Exception;
}

接口定義有如下特點:

  1. Mapper 接口方法名和Mapper.xml 中定義的statement 的id 相同
  2. Mapper 接口方法的輸入?yún)?shù)類型和mapper.xml 中定義的statement 的parameterType 的類型相同
  3. Mapper 接口方法的輸出參數(shù)類型和mapper.xml 中定義的statement 的resultType 的類型相同

2.4.4 加載UserMapper.xml 文件

修改SqlMapConfig.xml 文件:

<mappers>
        <!-- 加載mapper映射文件 -->
        <mapper resource="mapper/UserMapper.xml"></mapper>
    </mappers>

2.4.5 測試

package com.ghw.mapper;

import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;

import com.ghw.po.User;

public class UserMapperTest {

    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void before() throws IOException {
        // 讀取mybatis配置文件
        String resource = "SqlMapConfig.xml";
        // 得到配置文件流
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 創(chuàng)建會話工廠,傳入mybatis配置文件流
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 通過工廠得到SqlSession
    }

    // mapper根據(jù)id查詢用戶測試方法
    @Test
    public void testfindUserById() throws Exception {
        // 獲取sqlsession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 獲取mapper接口的代理對象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // 調(diào)用代理對象的方法
        User user = userMapper.findUserById(26);
        // 輸出查詢到的內(nèi)容
        System.out.println(user);
        // 關閉sqlsession
        sqlSession.close();
    }

    // mapper自定義條件查詢用戶列表
    @Test
    public void testfindUserByusername() throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // 通過mapper接口查詢用戶列表
        List<User> list = userMapper.findUserByusername("小明");
        System.out.println(list);
        // 關閉session
        sqlSession.close();
    }

    // mapper添加用戶
    @Test
    public void testinsertUser() throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user1 = new User();
        user1.setUsername("李書豪");
        user1.setBirthday(new Date());
        user1.setSex("男");
        user1.setAddress("陜西渭南");
        // 通過mapper接口添加用戶
        userMapper.insertUser(user1);
        // 提交
        sqlSession.commit();
        // 關閉session
        sqlSession.close();
    }

    // mapper刪除用戶
    @Test
    public void testdeleteUser() throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // 通過mapper接口刪除
        userMapper.deleteUser(30);
        // 提交
        sqlSession.commit();
        // 關閉session
        sqlSession.close();
    }

    // mapper修改用戶
    @Test
    public void testupdateUser() throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user2 = new User();
        user2.setId(28);
        user2.setBirthday(new Date());
        user2.setSex("女");
        user2.setUsername("李書豪1");
        user2.setAddress("西安郵電大學1");
        // 通過mapper接口修改用戶
        userMapper.updateUser(user2);
        // 提交
        sqlSession.commit();
        // 關閉session
        sqlSession.close();
    }
}

2.4.6 總結(jié)

  • selectOneselectList
    動態(tài)代理對象調(diào)用sqlSession.selectOne()sqlSession.selectList()是根據(jù)mapper 接口方法的返回值決定,如果返回List則調(diào)用selectList()方法,如果返回單個對象則調(diào)用selectOne()方法。
  • namespace
    mybatis官方推薦使用mapper代理方法開發(fā)mapper接口,程序員不用編寫mapper接口實現(xiàn)類,使用mapper代理方法時,輸入?yún)?shù)可以使用pojo包裝對象或map對象,保證dao的通用性。

3 SqlMapConfig.xml 配置文件

3.1 配置內(nèi)容

SqlMapConfig.xml 中配置的內(nèi)容和順序如下:
properties(屬性)
settings(全局配置參數(shù))
typeAliases(類型別名)
typeHandlers(類型處理器)
objectFactory(對象工廠)
plugins(插件)
environments(環(huán)境集合屬性對象)
    environment(環(huán)境子屬性對象)
        transactionManager(事務管理)
        dataSource(數(shù)據(jù)源)
mappers(映射器)

3.2 properties(屬性)

SqlMapConfig.xml可以引用java屬性文件中的配置信息如下:
classpath(類路徑,Source Folder文件夾下就是類路徑)下定義db.properties文件,內(nèi)容如下:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis001?characterEncoding=utf-8
jdbc.username=root
jdbc.password=admin

SqlMapConfig.xml引用如下:

<properties resource="db.properties"/>
    <environments default="development">
        <environment id="development">
            <!-- 使用jdbc事務管理 -->
            <transactionManager type="JDBC" />
            <!-- 數(shù)據(jù)庫連接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}" />
                <property name="url" value="${jdbc.url}" />
                <property name="username" value="${jdbc.username}" />
                <property name="password" value="${jdbc.password}" />
            </dataSource>
        </environment>
    </environments>

注意: MyBatis 將按照下面的順序來加載屬性:

  • properties元素體內(nèi)定義的屬性首先被讀取。
  • 然后會讀取properties元素中resourceurl加載的屬性,它會覆蓋已讀取的同名屬性。
  • 最后讀取parameterType傳遞的屬性,它會覆蓋已讀取的同名屬性。
  • 因此,通過parameterType傳遞的屬性具有最高優(yōu)先級,resourceurl加載的屬性次之,最低優(yōu)先級的是properties元素體內(nèi)定義的屬性。

3.3 settings(配置)

mybatis全局配置參數(shù),全局參數(shù)將會影響mybatis的運行行為。

3.4 typeAliases(類型別名)

3.4.1 mybatis 支持別名:

別名 映射的類型
_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

3.4.2 自定義別名:

SqlMapConfig.xml中配置:

<typeAliases>
        <!-- 單個別名定義 -->
        <typeAlias alias="user" type="com.ghw.po.User" />
        <!-- 批量別名定義,掃描整個包下的類,別名為類名(首字母大寫或小寫都可以) -->
        <package name="com.ghw.po" />
        <package name="其它包" />
</typeAliases>

3.5 typeHandlers(類型處理器)

類型處理器用于java類型和jdbc類型映射,如下:

<select id="findUserById" parameterType="int" resultType="user">
    select * from user where id = #{id}
</select>

mybatis 自帶的類型處理器基本上滿足日常需求,不需要單獨定義。
mybatis 支持類型處理器:

類型處理器 Java類型 JDBC類型
BooleanTypeHandler Boolean,boolean 任何兼容的布爾值
ByteTypeHandler Byte,byte 任何兼容的數(shù)字或字節(jié)類型
ShortTypeHandler Short,short 任何兼容的數(shù)字或短整型
IntegerTypeHandler Integer,int 任何兼容的數(shù)字和整型
LongTypeHandler Long,long 任何兼容的數(shù)字或長整型
FloatTypeHandler Float,float 任何兼容的數(shù)字或單精度浮點型
DoubleTypeHandler Double,double 任何兼容的數(shù)字或雙精度浮點型
BigDecimalTypeHandler BigDecimal 任何兼容的數(shù)字或十進制小數(shù)類型
StringTypeHandler String CHAR和VARCHAR類型
ClobTypeHandler String CLOB和LONGVARCHAR類型
NStringTypeHandler String NVARCHAR和NCHAR類型
NClobTypeHandler String NCLOB類型
ByteArrayTypeHandler byte[] 任何兼容的字節(jié)流類型
BlobTypeHandler byte[] BLOB和LONGVARBINARY類型
DateTypeHandler Date(java.util) TIMESTAMP類型
DateOnlyTypeHandler Date(java.util) DATE類型
TimeOnlyTypeHandler Date(java.util) TIME類型
SqlTimestampTypeHandler Timestamp(java.sql) TIMESTAMP類型
SqlDateTypeHandler Date(java.sql) DATE類型
SqlTimeTypeHandler Time(java.sql) TIME類型
ObjectTypeHandler 任意 其他或未指定類型
EnumTypeHandler Enumeration類型 VARCHAR-任何兼容的字符串類型,作為代碼存儲(而不是索引)。

3.6 mappers(映射器)

Mapper 配置的幾種方法:

3.6.1 <mapper resource=" " />

使用相對于類路徑的資源
如:<mapper resource="sqlmap/User.xml" />

3.6.2 <mapper url=" " />

使用完全限定路徑
如:<mapper url="file:///D:\workspace_spingmvc\mybatis_01\config\sqlmap\User.xml" />

3.6.3 <mapper class=" " />

使用mapper 接口類路徑
如:<mapper class="cn.itcast.mybatis.mapper.UserMapper"/>
注意:此種方法要求mapper 接口名稱和mapper 映射文件名稱相同,且放在同一個目錄中。

3.6.4 <package name=""/>

注冊指定包下的所有mapper 接口
如:<package name="cn.itcast.mybatis.mapper"/>
注意:此種方法要求mapper 接口名稱和mapper 映射文件名稱相同,且放在同一個目錄中。

4 Mapper.xml 映射文件

Mapper.xml 映射文件中定義了操作數(shù)據(jù)庫的sql,每個sql 是一個statement,映射文件是mybatis 的核心。

4.1 parameterType(輸入類型)

4.1.1 #{}與${}

#{}實現(xiàn)的是向prepareStatement 中的預處理語句中設置參數(shù)值,sql 語句中#{}表示一個占位
符即?。

<!-- 根據(jù)id獲取用戶信息 -->
<select id="findUserById" parameterType="int"
    resultType="com.ghw.po.User">
    select * from user where id = #{id}
</select>

使用占位符#{}可以有效防止sql 注入,在使用時不需要關心參數(shù)值的類型,mybatis 會自動
進行java 類型和jdbc 類型的轉(zhuǎn)換。#{}可以接收簡單類型值或pojo 屬性值,如果parameterType
傳輸單個簡單類型值,#{}括號中可以是value 或其它名稱。
${}和#{}不同,通過${}可以將parameterType 傳入的內(nèi)容拼接在sql 中且不進行jdbc 類型轉(zhuǎn)
換, ${}可以接收簡單類型值或pojo 屬性值,如果parameterType 傳輸單個簡單類型值,${}
括號中只能是value。使用${}不能防止sql 注入,但是有時用${}會非常方便,如下的例子:

<!-- 自定義條件查詢用戶列表 -->
<select id="findUserByusername" parameterType="String"
    resultType="com.ghw.po.User">
    select * from user where username like '%${value}%'
</select>

如果本例子使用#{}則傳入的字符串中必須有%號,而%是人為拼接在參數(shù)中,顯然有點麻煩,如果采用${}在sql 中拼接為%的方式則在調(diào)用mapper 接口傳遞參數(shù)就方便很多。

// 如果使用占位符號則必須人為在傳參數(shù)中加%
List<User> list = userMapper.selectUserByName("%管理員%");
// 如果使用${}原始符號則不用人為在參數(shù)中加%
List<User> list = userMapper.selectUserByName("管理員");

再比如order by 排序,如果將列名通過參數(shù)傳入sql,根據(jù)傳的列名進行排序,應該寫為:
ORDER BY ${columnName}
如果使用#{}將無法實現(xiàn)此功能。

4.1.2 傳遞簡單類型

參考上邊的例子。

4.1.3 傳遞pojo 對象

Mybatis 使用ognl 表達式解析對象字段的值,如下例子:

<!-- 傳遞pojo對象綜合查詢用戶信息 -->
<select id="findUserByUser" parameterType="user"
    resultType="user">
    select * from user where id=#{id} and username like '%${username}%'
</select>

上邊紅色標注的是user 對象中的字段名稱。
測試:

public void testFindUserByUser() throws Exception {
    // 獲取session
    SqlSession session = sqlSessionFactory.openSession();
    // 獲限mapper接口實例
    UserMapper userMapper = session.getMapper(UserMapper.class);
    // 構造查詢條件user對象
    User user = new User();
    user.setId(1);
    user.setUsername("管理員");
    // 傳遞user對象查詢用戶列表
    List<User> list = userMapper.findUserByUser(user);
    // 關閉session
    session.close();
}

異常測試:
Sql中字段名輸入錯誤后測試,username 輸入dusername 測試結(jié)果報錯:
org.apache.ibatis.exceptions.PersistenceException:

### Error querying database.  Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'dusername' in 'where clause'
### The error may exist in mapper/UserMapper.xml
### The error may involve com.ghw.mapper.UserMapper.findUserByusername-Inline
### The error occurred while setting parameters

4.1.4 傳遞pojo 包裝對象

開發(fā)中通過pojo 傳遞查詢條件,查詢條件是綜合的查詢條件,不僅包括用戶查詢條件
還包括其它的查詢條件(比如將用戶購買商品信息也作為查詢條件),這時可以使用包裝對象傳遞輸入?yún)?shù)。

4.1.4.1定義包裝對象

定義包裝對象將查詢條件(pojo)以類組合的方式包裝起來。

public class QueryVo {
    private User user;
    //自定義用戶擴展類
    private UserCustom userCustom;

4.1.4.2mapper.xml 映射文件

<!-- 查詢用戶列表 根據(jù)用戶名稱和用戶性別查詢用戶列表 -->
<select id="findUserList" parameterType="queryVo"
    resultType="user">
    select * from user where username = #{user.username} and sex = #{user.sex}
</select>

說明:mybatis 底層通過ognl 從pojo 中獲取屬性值:#{user.username},user 即是傳入的包裝對象的屬性。queryVo 是別名,即上邊定義的包裝對象類型。

說明:mybatis 底層通過 ognl 從 pojo 中獲取屬性值:#{user.username},user 即是傳入的包 裝對象的屬性。queryVo 是別名,即上邊定義的包裝對象類型。

4.1.5 傳遞hashmap

Sql 映射文件定義如下:

<!-- 傳遞hashmap綜合查詢用戶信息 -->
<select id="findUserByHashmap" parameterType="hashmap"
    resultType="user"> select * from user where id=#{id} and username like
    '%${username}%'
</select>

上面的id和username是hashmap的key

測試:

public void testFindUserByHashmap() throws Exception {
    // 獲取session
    SqlSession session = sqlSessionFactory.openSession();
    // 獲限mapper接口實例
    UserMapper userMapper = session.getMapper(UserMapper.class);
    // 構造查詢條件Hashmap對象
    HashMap<String, Object> map = new HashMap<String, Object>();
    map.put("id", 1);
    map.put("username", "管理員");
    // 傳遞Hashmap對象查詢用戶列表 List<User>list = userMapper.findUserByHashmap(map);
    // //關閉session session.close();
}

異常測試: 傳遞的 map 中的 key 和 sql 中解析的 key 不一致。 測試結(jié)果沒有報錯,只是通過 key 獲取值為空。

4.2 resultType(輸出類型)

4.2.1輸出簡單類型

參考 getnow 輸出日期類型,看下邊的例子輸出整型:
Mapper.xml 文件

<!-- 獲取用戶列表總數(shù) -->
<select id="findUserCount" parameterType="user" resultType="int">
    select count(1) from user
</select>

Mapper 接口
public int findUserCount(User user) throws Exception;
調(diào)用:

public void testFindUserCount() throws Exception {
    // 獲取session
    SqlSession session = sqlSessionFactory.openSession();
    // 獲取mapper接口實例
    UserMapper userMapper = session.getMapper(UserMapper.class);
    User user = new User();
    user.setUsername("管理員");
    // 傳遞Hashmap對象查詢用戶列表
    int count = userMapper.findUserCount(user);
    // 關閉session session.close();
}

總結(jié): 輸出簡單類型必須查詢出來的結(jié)果集有一條記錄,最終將第一個字段的值轉(zhuǎn)換為輸出類型。 使用 session 的 selectOne 可查詢單條記錄。

4.2.2輸出 pojo 對象

參考 findUserById 的定義:
Mapper.xml

<!-- 根據(jù)id查詢用戶信息 -->
<select id="findUserById" parameterType="int" resultType="user"> select
    * from user where id = #{id}
</select>

Mapper 接口:
public User findUserById(int id) throws Exception;
測試:

public void testFindUserById() throws Exception {
    // 獲取session
    SqlSession session = sqlSessionFactory.openSession();
    // 獲限mapper接口實例
    UserMapper userMapper = session.getMapper(UserMapper.class);
    // 通過mapper接口調(diào)用statement
    User user = userMapper.findUserById(1);
    System.out.println(user);
    // 關閉session
    session.close();
}

使用 session 調(diào)用 selectOne 查詢單條記錄。

4.2.3輸出 pojo 列表

參考 selectUserByName 的定義:
Mapper.xml

<!-- 根據(jù)名稱模糊查詢用戶信息 -->
<select id="findUserByUsername" parameterType="string"
    resultType="user"> select * from user where username like '%${value}%'
</select>

Mapper 接口:
public List<User> findUserByUsername(String username) throws Exception;
測試:

public void testFindUserByUsername() throws Exception {
    // 獲取session
    SqlSession session = sqlSessionFactory.openSession();
    // 獲限mapper接口實例
    UserMapper userMapper = session.getMapper(UserMapper.class);
    // 如果使用占位符號則必須人為在傳參數(shù)中加%
    // List<User> list = userMapper.selectUserByName("%管理員%");
    // 如果使用${}原始符號則不用人為在參數(shù)中加%
    List<User> list = userMapper.findUserByUsername("管理員");
    // 關閉session
    session.close();
}

使用 session 的 selectList 方法獲取 pojo 列表。

4.2.4 resultType 總結(jié):

輸出 pojo 對象和輸出 pojo 列表在 sql 中定義的 resultType 是一樣的。 返回單個pojo 對象要保證 sql 查詢出來的結(jié)果集為單條,內(nèi)部使用 session.selectOne 方法調(diào)用,mapper 接口使用 pojo 對象作為方法返回值。
返回 pojo 列表表示查詢出來的結(jié)果集可能為多條,內(nèi)部使用 session.selectList 方法,mapper 接口使用 List<pojo>對象作為方法返回值。

4.2.5 輸出 hashmap

輸出 pojo 對象可以改用 hashmap 輸出類型,將輸出的字段名稱作為 map 的 key,value 為字 段值。

4.3 resultMap

resultType 可以指定 pojo 將查詢結(jié)果映射為 pojo,但需要 pojo 的屬性名和 sql 查詢的列 名一致方可映射成功。 如果 sql 查詢字段名和 pojo 的屬性名不一致,可以通過 resultMap 將字段名和屬性名作 一個對應關系 ,resultMap 實質(zhì)上還需要將查詢結(jié)果映射到 pojo 對象中。 resultMap 可以實現(xiàn)將查詢結(jié)果映射為復雜類型的 pojo,比如在查詢結(jié)果映射對象中包 括 pojo 和 list 實現(xiàn)一對一查詢和一對多查詢。

4.3.1 Mapper.xml 定義

<!-- 查詢用戶列表 根據(jù)用戶名稱和用戶性別查詢用戶列表 -->
<select id="findUserListResultMap" parameterType="queryVo"
    resultMap="userListResultMap">
    select id id_,username username_,birthday birthday_ from user
    <!-- where自動將第一個and去掉 -->
    <where>
        <!-- refid:指定sql片段的id,如果要引用其他命名空間的sql片段,需要前邊加namaspcae -->
        <include refid="query_user_where"></include>
    </where>
</select>

使用 resultMap 指定上邊定義的 personmap。

4.3.2定義 resultMap

由于上邊的 mapper.xml 中 sql 查詢列和 Users.java 類屬性不一致,需要定義 resultMap: userListResultMap 將 sql 查詢列和 Users.java 類屬性對應起來

<!-- 定義resultMap,將用戶查詢的字段和user這個pojo的屬性作一個對應關系 -->
<!-- type:最終映射的java對象 id:resultMap的唯一標識 -->
<resultMap type="user" id="userListResultMap">
    <!-- id標簽:將查詢結(jié)果集的唯一標識列(主鍵或唯一標識) 
    column:sql查詢字段名(列名) 
    property:pojo屬性名 -->
    <id column="id_" property="id" />
    <result column="username_" property="username" />
    <result column="birthday_" property="birthday" />
</resultMap>

<id/>:此屬性表示查詢結(jié)果集的唯一標識,非常重要。如果是多個字段為復合唯一約束則 定義多個<id/>。
Property:表示 person 類的屬性。
Column:表示 sql 查詢出來的字段名。
Column 和 property 放在一塊兒表示將 sql 查詢出來的字段映射到指定的 pojo 類屬性上。
<result/>:普通結(jié)果,即 pojo 的屬性

4.3.3 Mapper 接口定義

public List<User> findUserListResultMap() throws Exception;

4.4 動態(tài) sql(重點)

通過 mybatis 提供的各種標簽方法實現(xiàn)動態(tài)拼接 sql。

4.4.1If

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

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