Spring Boot學習筆記04--數據訪問

摘要

看完本文你將掌握如下知識點:

  1. Spring Boot對JDBC的支持
  2. Spring Boot項目多數據源的配置
  3. Spring Boot的事務管理
  4. Spring Boot項目多數據源的事務管理
  5. Spring Boot項目中使用Hibernate4的方法
  6. Spring Boot項目中使用Mybatis的方法

SpringBoot系列Spring Boot學習筆記


前言

Spring Boot針對企業(yè)開發(fā)場景提供了各種『開箱即用』的spring-boot-starter-xxx自動配置依賴模塊捐康,這就使得我們開發(fā)Spring應用更加快速和高效蛋欣。比如我們前面創(chuàng)建web項目時使用到的spring-boot-starter-web旗们。

這些spring-boot-starter-xxx不但包含了對該功能的全部依賴包,同時也提供了該功能的自動配置類御滩。我們本節(jié)要討論的『數據訪問』就是基于這些spring-boot-starter-xxx的自動配置依賴模塊。


環(huán)境準備

jdk版本:java version "1.8.0_31"
數據庫:10.1.16-MariaDB
腳本

# 創(chuàng)建庫1
CREATE SCHEMA `springboot1` DEFAULT CHARACTER SET utf8 ;
CREATE TABLE `springboot1`.`person` (
  `p_id` INT NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `p_name` VARCHAR(45) NULL COMMENT '姓名',
  `p_age` INT NULL COMMENT '年齡',
  PRIMARY KEY (`p_id`))
ENGINE = InnoDB
COMMENT = '人員信息表';

INSERT INTO `springboot1`.`person` (`p_id`, `p_name`, `p_age`) VALUES ('1', '張三', '20');
INSERT INTO `springboot1`.`person` (`p_id`, `p_name`, `p_age`) VALUES ('2', '李四', '25');


# 創(chuàng)建庫2
CREATE SCHEMA `springboot2` DEFAULT CHARACTER SET utf8 ;
CREATE TABLE `springboot2`.`person` (
  `p_id` INT NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `p_name` VARCHAR(45) NULL COMMENT '姓名',
  `p_age` INT NULL COMMENT '年齡',
  PRIMARY KEY (`p_id`))
ENGINE = InnoDB
COMMENT = '人員信息表';

INSERT INTO `springboot2`.`person` (`p_id`, `p_name`, `p_age`) VALUES ('1', '張三', '20');
INSERT INTO `springboot2`.`person` (`p_id`, `p_name`, `p_age`) VALUES ('2', '李四', '25');

Spring Boot對JDBC的支持

創(chuàng)建項目

新建一個springboot項目他宛,依賴選擇web和jdbc


項目創(chuàng)建成功后查看pom奴愉,會看到添加了spring-boot-starter-jdbc的依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

配置項目

在pom中增加MySQL依賴

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.37</version>
</dependency>

application.properties中添加數據源配置信息

#datasource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot1?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=newpwd

項目代碼

本例只做簡單演示,所以只創(chuàng)建如下3個類嘶居,并用一個單元測試類進行測試
Model:Person

public class Person implements Serializable {
    private static final long serialVersionUID = -1L;
    private Long id;
    private String name;
    private Integer age;

    //getter and setter

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

Dao:PersonDao

@Repository
public class PersonDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public int savePerson(Person person){
        String sql = "INSERT INTO `springboot1`.`person` (`p_name`, `p_age`) VALUES (?, ?)";
        int result = jdbcTemplate.update(sql,new Object[]{person.getName(),person.getAge()});
        return result;
    }

    public List<Person> getAllPersonList(){
        String sql = "select * from person s";
        List<Person> list = jdbcTemplate.query(sql,new PersonMapper());
        return list;
    }

    class PersonMapper implements RowMapper<Person>{
        @Override
        public Person mapRow(ResultSet resultSet, int i) throws SQLException {
            Person person = new Person();
            person.setId(resultSet.getLong("p_id"));
            person.setName(resultSet.getString("p_name"));
            person.setAge(resultSet.getInt("p_age"));
            return person;
        }
    }
}

Service:PersonService

@Service
public class PersonService {

    @Autowired
    private PersonDao personDao;

    public int savePserson(Person person){
       return personDao.savePerson(person);

    }
    public List<Person> getAllPersonList(){
        return personDao.getAllPersonList();
    }
}

單元測試:SpringbootjdbcdemoApplicationTests

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootjdbcdemoApplicationTests {

    @Autowired
    private PersonService personService;

    @Test
    public void savePerson(){
        Person person = new Person();
        person.setName("王五");
        person.setAge(18);
        int result = personService.savePserson(person);
        Assert.assertEquals(1,result);
    }

    @Test
    public void getAllPersonList(){
        List<Person> list = personService.getAllPersonList();
        System.out.println(list.size());
        for(Person person : list){
            System.out.println(person);
        }
    }
}

說明
實際上罪帖,項目加入spring-boot-starter-jdbc的依賴后促煮,即可在項目代碼中通過@Autowired自動注入JdbcTemplate邮屁。而數據源的配置則在application.properties中進行配置。

如果不想使用spring-boot-starter-jdbc帶來的默認依賴和自動配置菠齿,那么采用如下的方式佑吝,效果是一樣的。


使用自定義的DataSourceConfig

修改pom中的依賴绳匀,去掉對spring-boot-starter-jdbc的依賴芋忿,并加入對spring-jdbc的依賴,這樣我們就失去了對JDBC的自動配置功能了疾棵。

    <!--
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    -->

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>4.3.4.RELEASE</version>
    </dependency>

啟動類中去掉對DataSourceAutoConfiguration的自動配置支持

@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
public class SpringbootjdbcdemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootjdbcdemoApplication.class, args);
    }
}

創(chuàng)建DataSourceConfig配置類

@Configuration
public class DataSourceConfig {

    @Value("${spring.datasource.driver-class-name}")
    String driverClass;
    @Value("${spring.datasource.url}")
    String url;
    @Value("${spring.datasource.username}")
    String userName;
    @Value("${spring.datasource.password}")
    String passWord;

    @Bean(name = "dataSource")
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driverClass);
        dataSource.setUrl(url);
        dataSource.setUsername(userName);
        dataSource.setPassword(passWord);
        return dataSource;
    }

    @Bean(name = "jdbcTemplate")
    public JdbcTemplate jdbcTemplate(){
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
        return jdbcTemplate;
    }
}

其它代碼不需要任何修改戈钢,運行效果一致。


說明
為什么SpringBoot為我們提供了spring-boot-starter-jdbc的自動配置解決方案是尔,我們還要自己配置呢殉了,這是因為自動配置并不是那么的強大,spring-boot-starter-jdbc只能支持單一的數據源配置拟枚,如果項目中需要關聯多個數據源薪铜,就需要我們自己處理了众弓。

比如我們在環(huán)境準備中創(chuàng)建了兩個數據庫,接下來我們在項目中增加多數據源的配置隔箍。


application.properties中添加數據源配置信息

#datasource2
spring.datasource.driver-class-name2=com.mysql.jdbc.Driver
spring.datasource.url2=jdbc:mysql://localhost:3306/springboot2?useUnicode=true&characterEncoding=utf-8
spring.datasource.username2=root
spring.datasource.password2=newpwd

然后在DataSourceConfig配置類中增加如下內容:

@Value("${spring.datasource.driver-class-name2}")
String driverClass2;
@Value("${spring.datasource.url2}")
String url2;
@Value("${spring.datasource.username2}")
String userName2;
@Value("${spring.datasource.password2}")
String passWord2;

@Bean(name = "dataSource2")
public DataSource dataSource2() {
    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setDriverClassName(driverClass2);
    dataSource.setUrl(url2);
    dataSource.setUsername(userName2);
    dataSource.setPassword(passWord2);
    return dataSource;
}

@Bean(name = "jdbcTemplate2")
public JdbcTemplate jdbcTemplate2(){
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource2());
    return jdbcTemplate;
}

此時需要在Dao中將@Autowired注解替換成@Resource(name = "jdbcTemplate")谓娃,來明確指定要使用哪一個jdbcTemplate對象。


說明
關于如何在項目中使用Hibernate4框架蜒滩,可以參考:SpringMVC4零配置


Spring Boot的事務管理

JDBC事務管理

如果我們項目中使用的是JDBC的數據訪問方案滨达,并且容器中只注冊了一個DataSource,那么SpringBoot就會為我們開啟DataSourceTransactionManagerAutoConfiguration的自動配置類俯艰,其會在容器中注冊一個DataSourceTransactionManager事務管理器族扰,同時會開啟對注解式事務@Transactional的支持。感興趣的可以看一下DataSourceTransactionManagerAutoConfiguration的源碼惊完。


@Transactional是Spring框架提供的膜蛔,配置方法參考下面的代碼

//一般我們會在業(yè)務實現類上聲明事務注解
//當前表示需要在事務中運行,可以執(zhí)行更新和刪除操作涩搓,遇到異常則回滾
@Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = { Exception.class })
public class PersonService{
    //方法上也可以標注事務注解污秆,方法上注解聲明會覆蓋類上的
    //一般查詢操作readOnly設置為true,增刪該操作設置為false
    @Transactional(readOnly = true)
    public List<Person> getAllPersonList(){
        //do something
    }

    //不加@Transactiona注解昧甘,則使用類上的設置
    public int savePserson(Person person){
        //do something
    }
}

如果在測試類上聲明@Transactional良拼,則會開啟自動回滾,不會產生臟數據

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class SpringbootjdbcdemoApplicationTests {…………}

如果希望自己配置事務充边,可以在配置類中增加事務管理器的配置庸推,比如,我們在DataSourceConfig中增加如下配置:

@Configuration
//啟用注解事務管理浇冰,使用CGLib代理
@EnableTransactionManagement(proxyTargetClass = true)
public class DataSourceConfig {

    @Value("${spring.datasource.driver-class-name}")
    String driverClass;
    @Value("${spring.datasource.url}")
    String url;
    @Value("${spring.datasource.username}")
    String userName;
    @Value("${spring.datasource.password}")
    String passWord;

    @Bean(name = "dataSource")
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driverClass);
        dataSource.setUrl(url);
        dataSource.setUsername(userName);
        dataSource.setPassword(passWord);
        return dataSource;
    }

    @Bean(name = "jdbcTemplate")
    public JdbcTemplate jdbcTemplate(){
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
        return jdbcTemplate;
    }

    @Bean
    public DataSourceTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }
}


說明
上面的方法只是針對單一數據源進行事務管理的贬媒,但是項目中經常會用到多數據源的情況,那么要如何進行事務管理呢肘习?


我們上文講到了可以在項目中通過配置類际乘,自己配置多個數據源,并通過DataSourceConfig進行了演示漂佩,接下來我們添加多個事務管理器脖含。

@Configuration
//啟用注解事務管理,使用CGLib代理
@EnableTransactionManagement(proxyTargetClass = true)
public class DataSourceConfig {

    @Value("${spring.datasource.driver-class-name}")
    String driverClass;
    @Value("${spring.datasource.url}")
    String url;
    @Value("${spring.datasource.username}")
    String userName;
    @Value("${spring.datasource.password}")
    String passWord;


    @Value("${spring.datasource.driver-class-name2}")
    String driverClass2;
    @Value("${spring.datasource.url2}")
    String url2;
    @Value("${spring.datasource.username2}")
    String userName2;
    @Value("${spring.datasource.password2}")
    String passWord2;

    @Bean(name = "dataSource")
    public DataSource dataSource() {

        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driverClass);
        dataSource.setUrl(url);
        dataSource.setUsername(userName);
        dataSource.setPassword(passWord);
        return dataSource;
    }

    @Bean(name = "jdbcTemplate")
    public JdbcTemplate jdbcTemplate(){
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
        return jdbcTemplate;
    }

    @Bean(name = "transactionManager")
    public DataSourceTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }

    @Bean(name = "dataSource2")
    public DataSource dataSource2() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driverClass2);
        dataSource.setUrl(url2);
        dataSource.setUsername(userName2);
        dataSource.setPassword(passWord2);
        System.out.println(url2);
        return dataSource;
    }

    @Bean(name = "jdbcTemplate2")
    public JdbcTemplate jdbcTemplate2(){
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource2());
        return jdbcTemplate;
    }

    @Bean(name = "transactionManager2")
    public DataSourceTransactionManager transactionManager2() {
        return new DataSourceTransactionManager(dataSource2());
    }
}

這時投蝉,我們必須在@Transactional注解中指定要使用哪一個事務管理器

@Service
@Transactional(transactionManager = "transactionManager",propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = { Exception.class })
public class PersonService {

    @Autowired
    private PersonDao personDao;

    public int savePserson(Person person){
        return personDao.savePerson(person);
    }
    @Transactional(transactionManager = "transactionManager",readOnly = true)
    public List<Person> getAllPersonList(){
        return personDao.getAllPersonList();
    }

    @Transactional(transactionManager = "transactionManager2",propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = { Exception.class })
    public int savePserson2(Person person){
        return personDao.savePerson2(person);
    }
    @Transactional(transactionManager = "transactionManager2",readOnly = true)
    public List<Person> getAllPersonList2(){
        return personDao.getAllPersonList2();
    }
}


說明
這樣做并不美好养葵,不能對多個數據源同時進行事務管理,比如瘩缆,我們在一個業(yè)務方法里同時對兩個數據源進行操作关拒,我們希望只要有一個發(fā)生異常,則兩個數據源的數據都進行回滾。

那要怎么做呢夏醉,我們接著往下看爽锥。


多數據源事務管理

這里推薦使用Atomikos,Atomikos支持Mysql畔柔、Oracle等多種數據庫氯夷,可與多種ORM框架集成,如MyBatis靶擦、JPA腮考、Hibernate等等,同時支持各種容器下JNDI的多數據源管理玄捕。Atomikos官網提供了各種情況下使用Atomikos的Example踩蔚,本文只對使用JDBC時的情況進行說明。

目前maven中央倉庫的最新版本是4.0.4枚粘,使用Atomikos馅闽,需要在項目中加入如下依賴:

<dependency>
    <groupId>com.atomikos</groupId>
    <artifactId>transactions-jdbc</artifactId>
    <version>4.0.4</version>
</dependency>
<dependency>
    <groupId>com.atomikos</groupId>
    <artifactId>transactions-jta</artifactId>
    <version>4.0.4</version>
</dependency>
<dependency>
    <groupId>com.atomikos</groupId>
    <artifactId>transactions</artifactId>
    <version>4.0.4</version>
</dependency>
<dependency>
    <groupId>com.atomikos</groupId>
    <artifactId>atomikos-util</artifactId>
    <version>4.0.4</version>
</dependency>
 <dependency>
    <groupId>javax.transaction</groupId>
    <artifactId>jta</artifactId>
    <version>1.1</version>
</dependency>

DataSourceConfig進行改造:

package com.example;

import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.jta.JtaTransactionManager;
import javax.sql.DataSource;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;

@Configuration
//啟用注解事務管理,使用CGLib代理
@EnableTransactionManagement(proxyTargetClass = true)
public class DataSourceConfig {

    @Value("${spring.datasource.driver-class-name}")
    String driverClass;
    @Value("${spring.datasource.url}")
    String url;
    @Value("${spring.datasource.username}")
    String userName;
    @Value("${spring.datasource.password}")
    String passWord;


    @Value("${spring.datasource.driver-class-name2}")
    String driverClass2;
    @Value("${spring.datasource.url2}")
    String url2;
    @Value("${spring.datasource.username2}")
    String userName2;
    @Value("${spring.datasource.password2}")
    String passWord2;


    @Bean(name = "userTransaction")
    public UserTransaction userTransaction() throws Throwable {
        UserTransactionImp userTransactionImp = new UserTransactionImp();
        userTransactionImp.setTransactionTimeout(300);
        return userTransactionImp;
    }

    @Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
    public TransactionManager atomikosTransactionManager() throws Throwable {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        userTransactionManager.setForceShutdown(true);
        return userTransactionManager;
    }

    @Bean(name = "transactionManager")
    @DependsOn({ "userTransaction", "atomikosTransactionManager" })
    public PlatformTransactionManager transactionManager() throws Throwable {
        UserTransaction userTransaction = userTransaction();
        TransactionManager atomikosTransactionManager = atomikosTransactionManager();
        JtaTransactionManager jtaTransactionManager = new JtaTransactionManager(userTransaction, atomikosTransactionManager);
        jtaTransactionManager.setAllowCustomIsolationLevels(true);
        return jtaTransactionManager;
    }

    @Bean(name = "dataSource", initMethod = "init", destroyMethod = "close")
    public DataSource dataSource() {
        System.out.println("dataSource init");
        //Oracle:oracle.jdbc.xa.client.OracleXADataSource
        //Druid:com.alibaba.druid.pool.xa.DruidXADataSource
        //Postgresql:org.postgresql.xa.PGXADataSource
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setUrl(url);
        mysqlXaDataSource.setPassword(passWord);
        mysqlXaDataSource.setUser(userName);
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName("dataSource");
        xaDataSource.setMinPoolSize(10);
        xaDataSource.setPoolSize(10);
        xaDataSource.setMaxPoolSize(30);
        xaDataSource.setBorrowConnectionTimeout(60);
        xaDataSource.setReapTimeout(20);
        xaDataSource.setMaxIdleTime(60);
        xaDataSource.setMaintenanceInterval(60);
        return xaDataSource;
    }

    @Bean(name = "dataSource2", initMethod = "init", destroyMethod = "close")
    public DataSource dataSource2() {
        System.out.println("dataSource2 init");
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setUrl(url2);
        mysqlXaDataSource.setPassword(passWord2);
        mysqlXaDataSource.setUser(userName2);
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName("dataSource2");
        xaDataSource.setMinPoolSize(10);
        xaDataSource.setPoolSize(10);
        xaDataSource.setMaxPoolSize(30);
        xaDataSource.setBorrowConnectionTimeout(60);
        xaDataSource.setReapTimeout(20);
        xaDataSource.setMaxIdleTime(60);
        xaDataSource.setMaintenanceInterval(60);
        return xaDataSource;
    }

    @Bean(name = "jdbcTemplate")
    public JdbcTemplate jdbcTemplate(){
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
        return jdbcTemplate;
    }


    @Bean(name = "jdbcTemplate2")
    public JdbcTemplate jdbcTemplate2(){
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource2());
        return jdbcTemplate;
    }
}

項目編譯路徑下可以創(chuàng)建一個jta.properties文件馍迄,用于對Atomikos的相關屬性進行配置福也,不過也可以不加這個文件,因為所有的屬性都有默認值攀圈。

com.atomikos.icatch.enable_logging=true
com.atomikos.icatch.force_shutdown_on_vm_exit=false
com.atomikos.icatch.automatic_resource_registration=true
com.atomikos.icatch.checkpoint_interval=500
com.atomikos.icatch.serial_jta_transactions=true
com.atomikos.icatch.default_jta_timeout=10000
com.atomikos.icatch.max_timeout=300000
com.atomikos.icatch.log_base_dir=./
com.atomikos.icatch.threaded_2pc=false
com.atomikos.icatch.max_actives=50
com.atomikos.icatch.log_base_name=tmlog
java.naming.factory.initial=com.sun.jndi.rmi.registry.RegistryContextFactory
com.atomikos.icatch.client_demarcation=false
java.naming.provider.url=rmi://localhost:1099
com.atomikos.icatch.rmi_export_class=none
com.atomikos.icatch.trust_client_tm=false
com.atomikos.icatch.forget_orphaned_log_entries_delay=86400000
com.atomikos.icatch.recovery_delay=${com.atomikos.icatch.default_jta_timeout}
com.atomikos.icatch.oltp_max_retries=5
com.atomikos.icatch.oltp_retry_interval=10000
com.atomikos.icatch.allow_subtransactions=true

Spring Boot中Atomikos與Hibernate4多數據源集成方法

Atomikos與Hibernate4集成方法與JDBC類似暴凑,我們在pom中加入hibernate的依賴,并對DataSourceConfig進行改造
pom

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>4.3.5.Final</version>
</dependency>

DataSourceConfig

package com.example;

import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.example.hibernate.CP_HibernateDAO;
import com.example.hibernate.impl.CP_Hibernate4DAOImpl;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.orm.hibernate4.LocalSessionFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.jta.JtaTransactionManager;

import javax.sql.DataSource;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
import java.util.Properties;

@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
public class DataSourceConfig {

    @Value("${spring.datasource.driver-class-name}")
    String driverClass;
    @Value("${spring.datasource.url}")
    String url;
    @Value("${spring.datasource.username}")
    String userName;
    @Value("${spring.datasource.password}")
    String passWord;


    @Value("${spring.datasource.driver-class-name2}")
    String driverClass2;
    @Value("${spring.datasource.url2}")
    String url2;
    @Value("${spring.datasource.username2}")
    String userName2;
    @Value("${spring.datasource.password2}")
    String passWord2;


    @Bean(name = "userTransaction")
    public UserTransaction userTransaction() throws Throwable {
        UserTransactionImp userTransactionImp = new UserTransactionImp();
        userTransactionImp.setTransactionTimeout(10000);
        return userTransactionImp;
    }

    @Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
    public TransactionManager atomikosTransactionManager() throws Throwable {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        userTransactionManager.setForceShutdown(true);
        return userTransactionManager;
    }

    @Bean(name = "transactionManager")
    @DependsOn({ "userTransaction", "atomikosTransactionManager" })
    public PlatformTransactionManager transactionManager() throws Throwable {
        System.out.println();
        UserTransaction userTransaction = userTransaction();
        TransactionManager atomikosTransactionManager = atomikosTransactionManager();
        JtaTransactionManager jtaTransactionManager = new JtaTransactionManager(userTransaction, atomikosTransactionManager);
        jtaTransactionManager.setAllowCustomIsolationLevels(true);

        return jtaTransactionManager;
    }

    @Bean(name = "dataSource", initMethod = "init", destroyMethod = "close")
    public DataSource dataSource() {
        System.out.println();

        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setUrl(url);
        mysqlXaDataSource.setPassword(passWord);
        mysqlXaDataSource.setUser(userName);
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName("dataSource");
        xaDataSource.setMinPoolSize(10);
        xaDataSource.setPoolSize(10);
        xaDataSource.setMaxPoolSize(30);
        xaDataSource.setBorrowConnectionTimeout(60);
        xaDataSource.setReapTimeout(20);
        xaDataSource.setMaxIdleTime(60);
        xaDataSource.setMaintenanceInterval(60);

        return xaDataSource;
    }

    @Bean(name = "dataSource2", initMethod = "init", destroyMethod = "close")
    public DataSource dataSource2() {
        System.out.println();
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setUrl(url2);
        mysqlXaDataSource.setPassword(passWord2);
        mysqlXaDataSource.setUser(userName2);
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName("dataSource2");
        xaDataSource.setMinPoolSize(10);
        xaDataSource.setPoolSize(10);
        xaDataSource.setMaxPoolSize(30);
        xaDataSource.setBorrowConnectionTimeout(60);
        xaDataSource.setReapTimeout(20);
        xaDataSource.setMaxIdleTime(60);
        xaDataSource.setMaintenanceInterval(60);
        return xaDataSource;
    }


    @Bean(name = "sessionFactory")
    public LocalSessionFactoryBean localSessionFactoryBean() {
        System.out.println("sessionFactory");
        LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
        sessionFactory.setDataSource(dataSource());
        //掃描實體對象的目錄赘来,不同的數據源现喳,實體要存放不同的目錄
        String[] packagesToScan = new String[] { "com.example.model.ds1" };
        sessionFactory.setPackagesToScan(packagesToScan);

        Properties hibernateProperties = new Properties();
        hibernateProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
        hibernateProperties.setProperty("hibernate.show_sql", "true");

        //開啟Hibernate對JTA的支持
        hibernateProperties.setProperty("hibernate.current_session_context_class", "jta");
        hibernateProperties.setProperty("hibernate.transaction.factory_class", "org.hibernate.transaction.JTATransactionFactory");

        sessionFactory.setHibernateProperties(hibernateProperties);

        return sessionFactory;

    }

    @Bean(name = "sessionFactory2")
    public LocalSessionFactoryBean localSessionFactoryBean2() {
        System.out.println("sessionFactory2");
        LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
        sessionFactory.setDataSource(dataSource2());
        //掃描實體對象的目錄,不同的數據源犬辰,實體要存放不同的目錄
        String[] packagesToScan = new String[] { "com.example.model.ds2" };
        sessionFactory.setPackagesToScan(packagesToScan);

        Properties hibernateProperties = new Properties();
        hibernateProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
        hibernateProperties.setProperty("hibernate.show_sql", "true");

        //開啟Hibernate對JTA的支持
        hibernateProperties.setProperty("hibernate.current_session_context_class", "jta");
        hibernateProperties.setProperty("hibernate.transaction.factory_class", "org.hibernate.transaction.JTATransactionFactory");

        sessionFactory.setHibernateProperties(hibernateProperties);

        return sessionFactory;

    }

    @Bean(name = "hibernateDAO")
    public CP_HibernateDAO hibernate4Dao() {
        System.out.println("hibernateDAO");
        CP_Hibernate4DAOImpl dao = new CP_Hibernate4DAOImpl();
        //綁定SessionFactory
        dao.setSessionFactory(localSessionFactoryBean().getObject());
        return dao;
    }

    @Bean(name = "hibernateDAO2")
    public CP_HibernateDAO hibernate4Dao2() {
        System.out.println("hibernateDAO2");
        CP_Hibernate4DAOImpl dao = new CP_Hibernate4DAOImpl();
        //綁定SessionFactory2
        dao.setSessionFactory(localSessionFactoryBean2().getObject());
        return dao;
    }
}
@Entity
@Table(name = "person")
public class Person implements Serializable {
    private static final long serialVersionUID = -1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "p_id")
    private Long id;
    @Column(name = "p_name")
    private String name;
    @Column(name = "p_age")
    private Integer age;
    //setter and getter
}

CP_HibernateDAO是我們自定義的Hibernate的通用Dao接口嗦篱,其定義的方法和和實現類CP_Hibernate4DAOImpl代碼如下:

package com.example.hibernate;

import java.util.List;

public interface CP_HibernateDAO {

    public List<?> findAll(Class<?> entityClazz, String... str);

    public void save(Object entity);

}
package com.example.hibernate.impl;

import com.example.hibernate.CP_HibernateDAO;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.DetachedCriteria;

import java.util.List;

public class CP_Hibernate4DAOImpl implements CP_HibernateDAO {


    private SessionFactory sessionFactory;

    public SessionFactory getSessionFactory() {
        return sessionFactory;
    }
    //綁定SessionFactory
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }


    private Session getHibernateSession() {
        Session session = sessionFactory.openSession();
        return session;
    }


    /*
     * @see com.example.hibernate.CP_HibernateDAO#findAll()
     */
    @Override
    public List<?> findAll(Class<?> entityClazz, String... str) {
        DetachedCriteria dc = DetachedCriteria.forClass(entityClazz);
        List<?> list = findAllByCriteria(dc);
        return list;
    }


    /*
     * @see com.example.hibernate.CP_HibernateDAO#save(java.lang.Object)
     */
    @Override
    public void save(Object entity) {

        getHibernateSession().save(entity);
        //注意這里一定要執(zhí)行flush方法
        getHibernateSession().flush();
    }


    public List<?> findAllByCriteria(DetachedCriteria detachedCriteria) {
        // TODO Auto-generated method stub
        Criteria criteria = detachedCriteria
                .getExecutableCriteria(getHibernateSession());
        return criteria.list();
    }

}

說明
需要注意兩點:

  1. session必須使用sessionFactory.openSession()的方式獲得,不能使用sessionFactory.getCurrentSession()忧风。
  2. 更新操作必須調用session.flush()方法默色。

Spring配置文件的方式球凰,可以參考:Spring4+Hibernate4+Atomikos3.3多數據源事務管理


Spring Boot中Mybitas的使用

創(chuàng)建項目時狮腿,我們可以選擇mybatis-spring-boot-starter依賴,這樣可以激活SpringBoot對Mybatis的自動配置類呕诉。

pom中添加依賴

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.1.1</version>
</dependency>

application.properties中添加mybaits的自動配置屬性缘厢,可以查看MybatisProperties了解可以配置哪些屬性

#mapper配置文件路徑,如果是基于注解的形式可以不需要配置該屬性
mybatis.mapper-locations=classpath:mapper/*.xml

Mapper接口上要配置@Mapper注解甩挫,因為mybatis-spring-boot-starter的自動配置會掃描@Mapper注解來注冊Mapper接口贴硫。

@Mapper
public interface PersonMapper {
    //………………
}

此時同樣可以使用@Transactional注解


說明
可以使用maven的mybatis-generator插件自動生成代碼,參考maven插件--MyBatis自動生成代碼


mybatis-spring-boot-starter不利于擴展,所以還是我們自己實現個mybitas的配置類吧英遭。

pom中去掉mybatis-spring-boot-starter的依賴间护,增加mybatis的依賴

        <!--
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.1.1</version>
        </dependency>-->

        <!--Mybatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.3.4.RELEASE</version>
        </dependency>

創(chuàng)建MyBatisConfig

@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
public class MyBatisConfig {

    @Value("${spring.datasource.driver-class-name}")
    String driverClass;
    @Value("${spring.datasource.url}")
    String url;
    @Value("${spring.datasource.username}")
    String userName;
    @Value("${spring.datasource.password}")
    String passWord;

    @Bean(name = "dataSource")
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driverClass);
        dataSource.setUrl(url);
        dataSource.setUsername(userName);
        dataSource.setPassword(passWord);
        return dataSource;
    }

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactoryBean() {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource());

        //添加XML目錄
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        try {
            bean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
            return bean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }
}

MyBatisMapperScannerConfig,基于包掃描Mapper挖诸,此時不需要配置@Mapper注解

@Configuration
//必須在MyBatisConfig注冊后再加載MapperScannerConfigurer汁尺,否則會報錯
@AutoConfigureAfter(MyBatisConfig.class)
public class MyBatisMapperScannerConfig {
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
        mapperScannerConfigurer.setBasePackage("com.example.mapper");
        return mapperScannerConfigurer;
    }
}

關閉DataSourceAutoConfiguration,因為這里我們配置了數據源多律,所以需要關閉該自動配置痴突,另外,MybatisAutoConfiguration也是基于DataSourceAutoConfiguration的狼荞,所以關閉了DataSourceAutoConfiguration也就同時關閉了MybatisAutoConfiguration辽装。


Spring Boot中Atomikos與Mybatis多數據源集成方法

pom

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.3.4.RELEASE</version>
        </dependency>

        <!--Mybatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.0</version>
        </dependency>

        <dependency>
            <groupId>com.atomikos</groupId>
            <artifactId>transactions-jdbc</artifactId>
            <version>4.0.4</version>
        </dependency>
        <dependency>
            <groupId>com.atomikos</groupId>
            <artifactId>transactions-jta</artifactId>
            <version>4.0.4</version>
        </dependency>
        <dependency>
            <groupId>com.atomikos</groupId>
            <artifactId>transactions</artifactId>
            <version>4.0.4</version>
        </dependency>
        <dependency>
            <groupId>com.atomikos</groupId>
            <artifactId>atomikos-util</artifactId>
            <version>4.0.4</version>
        </dependency>

        <dependency>
            <groupId>javax.transaction</groupId>
            <artifactId>jta</artifactId>
            <version>1.1</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.37</version>
        </dependency>

MyBatisConfig

@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
public class MyBatisConfig {

    @Value("${spring.datasource.driver-class-name}")
    String driverClass;
    @Value("${spring.datasource.url}")
    String url;
    @Value("${spring.datasource.username}")
    String userName;
    @Value("${spring.datasource.password}")
    String passWord;



    @Value("${spring.datasource.driver-class-name2}")
    String driverClass2;
    @Value("${spring.datasource.url2}")
    String url2;
    @Value("${spring.datasource.username2}")
    String userName2;
    @Value("${spring.datasource.password2}")
    String passWord2;


    @Bean(name = "userTransaction")
    public UserTransaction userTransaction() throws Throwable {
        UserTransactionImp userTransactionImp = new UserTransactionImp();
        userTransactionImp.setTransactionTimeout(10000);
        return userTransactionImp;
    }

    @Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
    public TransactionManager atomikosTransactionManager() throws Throwable {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        userTransactionManager.setForceShutdown(true);
        return userTransactionManager;
    }

    @Bean(name = "transactionManager")
    @DependsOn({ "userTransaction", "atomikosTransactionManager" })
    public PlatformTransactionManager transactionManager() throws Throwable {
        UserTransaction userTransaction = userTransaction();
        TransactionManager atomikosTransactionManager = atomikosTransactionManager();
        JtaTransactionManager jtaTransactionManager = new JtaTransactionManager(userTransaction, atomikosTransactionManager);
        jtaTransactionManager.setAllowCustomIsolationLevels(true);
        return jtaTransactionManager;
    }

    @Bean(name = "dataSource", initMethod = "init", destroyMethod = "close")
    public DataSource dataSource() {
        System.out.println("dataSource init");
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setUrl(url);
        mysqlXaDataSource.setPassword(passWord);
        mysqlXaDataSource.setUser(userName);
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName("dataSource");
        xaDataSource.setMinPoolSize(10);
        xaDataSource.setPoolSize(10);
        xaDataSource.setMaxPoolSize(30);
        xaDataSource.setBorrowConnectionTimeout(60);
        xaDataSource.setReapTimeout(20);
        xaDataSource.setMaxIdleTime(60);
        xaDataSource.setMaintenanceInterval(60);

        return xaDataSource;
    }

    @Bean(name = "dataSource2", initMethod = "init", destroyMethod = "close")
    public DataSource dataSource2() {
        System.out.println("dataSource2 init");
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setUrl(url2);
        mysqlXaDataSource.setPassword(passWord2);
        mysqlXaDataSource.setUser(userName2);
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName("dataSource2");
        xaDataSource.setMinPoolSize(10);
        xaDataSource.setPoolSize(10);
        xaDataSource.setMaxPoolSize(30);
        xaDataSource.setBorrowConnectionTimeout(60);
        xaDataSource.setReapTimeout(20);
        xaDataSource.setMaxIdleTime(60);
        xaDataSource.setMaintenanceInterval(60);
        return xaDataSource;
    }

    //基于xml式Mapper
    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactoryBean() {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource());

        //添加Mapper配置文件的目錄
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        try {
            bean.setMapperLocations(resolver.getResources("classpath:mapper/ds1/*.xml"));
            return bean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    @Bean(name = "sqlSessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate() {
        return new SqlSessionTemplate(sqlSessionFactoryBean());
    }

    //基于注解式Mapper
    @Bean(name = "sqlSessionFactory2")
    public SqlSessionFactory sqlSessionFactoryBean2() {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource2());
        try {
            return bean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    @Bean(name = "sqlSessionTemplate2")
    public SqlSessionTemplate sqlSessionTemplate2() {
        return new SqlSessionTemplate(sqlSessionFactoryBean2());
    }
}

MyBatisMapperScannerConfig

@Configuration
//必須在MyBatisConfig注冊后再加載MapperScannerConfigurer,否則會報錯
@AutoConfigureAfter(MyBatisConfig.class)
public class MyBatisMapperScannerConfig {
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        //綁定datasorce的sqlSessionFactory
        mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
        //掃描ds1目錄來注冊Mapper接口
        mapperScannerConfigurer.setBasePackage("com.example.mapper.ds1");
        return mapperScannerConfigurer;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer2() {
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        //綁定datasorce2的sqlSessionFactory
        mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory2");
        //掃描ds2目錄來注冊Mapper接口
        mapperScannerConfigurer.setBasePackage("com.example.mapper.ds2");
        return mapperScannerConfigurer;
    }
}

這里要說明的是相味,如果兩個數據源下的Mapper起了相同的類名拾积,雖然他們在不同的包路徑下,啟動也會報錯了丰涉,因為默認注冊Mapper時使用的是類名稱(不含包名)殷勘,此時可以在Mapper上加上@Component("personMapper")注解


寫在后面的話

Spring Boot為我們提供了大量的spring-boot-starter-xxx來加快我們的開發(fā)流程,創(chuàng)建項目時就可以看到可供選擇的各種spring-boot-starter-xxx昔搂,那么這么多的spring-boot-starter-xxx玲销,我們是否都需要了解呢,如果項目中需要用到某一個功能摘符,是否就應該加入這個spring-boot-starter-xxx呢贤斜?

筆者人為,spring-boot-starter-xxx提供的完整jar包依賴和自動配置固然很好逛裤,但是當我們要在項目中加入某一個功能時瘩绒,作為開發(fā)人員,是應該清楚的知道該功能的依賴關系和配置邏輯的带族,所以并不一定需要引入SpringBoot的spring-boot-starter-xxx锁荔,而且SpringBoot對這些spring-boot-starter-xxx做的自動配置,如果我們并不熟悉和十分清楚蝙砌,往往會給我們開發(fā)人員造成不明所以的困擾阳堕,所以,筆者建議择克,在對SpringBoot提供的某一個spring-boot-starter-xxx所提供的功能并不十分清楚時恬总,還是使用配置類的方式吧。

還有肚邢,由于某些自動配置類的激活是根據項目中是否包含某個class或容器中是否注冊了某個bean壹堰,所以筆者建議拭卿,如果項目中引入了新的jar包,或者手工注冊了某個bean贱纠,都要通過debug的方式查看是否開啟了某個自動配置峻厚。

另外,本文代碼只是為了輔助說明谆焊,比如DriverManagerDataSource正式環(huán)境不建議使用目木,請更換為其它數據源,比如BasicDataSource懊渡。

本文示例代碼下載地址:https://github.com/hanqunfeng/SpringBootStudy

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末刽射,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子剃执,更是在濱河造成了極大的恐慌誓禁,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肾档,死亡現場離奇詭異摹恰,居然都是意外死亡,警方通過查閱死者的電腦和手機怒见,發(fā)現死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門俗慈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人遣耍,你說我怎么就攤上這事闺阱。” “怎么了舵变?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵酣溃,是天一觀的道長。 經常有香客問我纪隙,道長赊豌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任绵咱,我火速辦了婚禮碘饼,結果婚禮上,老公的妹妹穿的比我還像新娘悲伶。我一直安慰自己艾恼,他們只是感情好,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布拢切。 她就那樣靜靜地躺著蒂萎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪淮椰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天,我揣著相機與錄音主穗,去河邊找鬼泻拦。 笑死,一個胖子當著我的面吹牛忽媒,可吹牛的內容都是我干的争拐。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼晦雨,長吁一口氣:“原來是場噩夢啊……” “哼架曹!你這毒婦竟也來了?” 一聲冷哼從身側響起闹瞧,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤绑雄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后奥邮,有當地人在樹林里發(fā)現了一具尸體万牺,經...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年洽腺,在試婚紗的時候發(fā)現自己被綠了脚粟。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蘸朋,死狀恐怖核无,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情藕坯,我是刑警寧澤厕宗,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站堕担,受9級特大地震影響已慢,放射性物質發(fā)生泄漏。R本人自食惡果不足惜霹购,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一佑惠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧齐疙,春花似錦膜楷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至轿塔,卻和暖如春特愿,著一層夾襖步出監(jiān)牢的瞬間仲墨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工揍障, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留目养,地道東北人。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓毒嫡,卻偏偏與公主長得像癌蚁,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子兜畸,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內容