Spring Data

什么是Spring Data?
  • 主旨:provide a familiar and consistent,Spring-based programming model for data access
  • 历史:2010,作者Rod Johnso,Spring Source 项目
  • 官网:spring data
Spring Data概览
Spring Data 应用场景

Spring Data包含多个子项目:

  • Spring Data JPA
  • Spring Data Mongo DB
  • Spring Data Redis
  • Spring Data Solr
  • ...

操作数据库

  • 传统方式访问数据库
  • Spring Data快速起步
  • Spring Data JPA进阶
  • Spring Data JPA高级

传统方式访问数据库

  1. JDBC
  2. Spring JdbcTemplate
  3. 弊端分析
其一:传统方式访问数据库之JDBC
  • Connection
  • Statement
  • ResultSet
  • Test Case

JDBC Demo

  1. 创建Maven项目
    maven工程主要配置文件pom.xml,用来配置项目
    添加依赖
<!--MySQL Driver-->
  <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.38</version>
</dependency>
<!--junit-->
<dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>4.10</version>
</dependency>

2.数据表准备

create database spring_data;
create table student(
id int not null auto_increment,
name varchar(20) not null,
age int not null,
 primary key(id)
);
insert into student(name,age) values("zhangsan",20);
insert into student(name,age) values("lisi",20);
insert into student(name,age) values("wangwu",20);

3.开发JDBCUtil工具类
获取Connection,关闭Connection、Statement、ResultSet
注意事项:配置的属性放在配置文件中,然后通过代码的方式将配置文件中的数据加载进来即可。

***JDBCUtil.java ***

import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

/**
 * JDBC工具类:
 * 1)获取Connection
 * 2)释放资源
 */
public class JDBCUtil {

    /**
     * 获取Connection
     * @return 所获得到的JDBC的Connection
     */
    public static Connection getConnection() throws Exception {
        /*String url = "jdbc:mysql://localhost:3306/spring_data";
        String user = "root";
        String password = "root";
        String driverClass = "com.mysql.jdbc.Driver";*/

        InputStream inputStream = JDBCUtil.class.getClassLoader().getResourceAsStream("db.properties");
        Properties properties = new Properties();
        properties.load(inputStream);

        String url = properties.getProperty("jdbc.url");
        String user = properties.getProperty("jdbc.user");
        String password = properties.getProperty("jdbc.password");
        String driverClass = properties.getProperty("jdbc.driverClass");

        Class.forName(driverClass);
        Connection connection = DriverManager.getConnection(url,user,password);
        return connection;
    }

    public static void release(ResultSet resultSet, Statement statement,Connection connection){
        if(resultSet != null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(statement != null){
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(connection != null){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

db.properties

jdbc.url = jdbc:mysql://localhost:3306/spring_data
jdbc.user = root
jdbc.password = root
jdbc.driverClass = com.mysql.jdbc.Driver

4.domain-->dao-->daoImpl

Student.java

public class Student {

    private int id;

    private String name;

    private int age;

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

StudentDao.java

public interface StudentDao {

    /**
     * 查询所有学生
     * @return
     */
    public List<Student> query();

    /**
     * 添加学生
     * @param student
     */
    public void save(Student student);

}

StudentDaoImpl.java

public class StudentDaoImpl implements StudentDao {
    @Override
    public List<Student> query() {
        List<Student> students = new ArrayList<Student>();
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        String sql = "select id,name,age from student";
        try {
            connection = JDBCUtil.getConnection();
            preparedStatement = connection.prepareStatement(sql);
            resultSet = preparedStatement.executeQuery();
            Student student = null;
            while(resultSet.next()){
                int id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                int age = resultSet.getInt("age");
                student = new Student();
                student.setId(id);
                student.setName(name);
                student.setAge(age);
                students.add(student);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
          JDBCUtil.release(resultSet,preparedStatement,connection);
        }
        return students;
    }

    @Override
    public void save(Student student) {

        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        String sql = "insert into student(name,age) values(?,?)";

        try {
            connection = JDBCUtil.getConnection();
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1,student.getName());
            preparedStatement.setInt(2,student.getAge());
            preparedStatement.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtil.release(resultSet,preparedStatement,connection);
        }
    }

}

以上属于使用传统方式访问数据库之JDBC,紧接来使用Spring JdbcTemplate来访问数据库

其二:传统方式访问数据库之Spring JdbcTemplate

  • Maven依赖
  • DataSource & JdbcTemplate注入
  • Test Case
使用Spring jdbc的方式操作数据库
  • 添加依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>4.3.5.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.2.0.RELEASE</version>
</dependency>
  • 配置beans.xml
  • 开发spring jdbc版本的query和save方法

beans.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id = "dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring_data"/>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="studentDao" class="com.imooc.dao.StudentDaoSpringJdbcTemplate">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
</beans>

StudentDaoSpringJdbcTemplate.java

public class StudentDaoSpringJdbcTemplate implements StudentDao{

    private JdbcTemplate jdbcTemplate;

    @Override
    public List<Student> query() {
        List<Student> students = new ArrayList<Student>();
        String sql = "select id,name,age from student";

        jdbcTemplate.query(sql, new RowCallbackHandler() {
            @Override
            public void processRow(ResultSet resultSet) throws SQLException {
                int id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                int age = resultSet.getInt("age");

                Student student = new Student();
                student.setId(id);
                student.setName(name);
                student.setAge(age);

                students.add(student);
            }
        });

        return students;
    }

    @Override
    public void save(Student student) {
        String sql = "insert into student(name,age) values(?,?)";
        jdbcTemplate.update(sql,new Object[]{student.getName(),student.getAge()});
    }

    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
}

以上属于使用传统方式访问数据库之Spring JdbcTemplate
以上两种传统方式访问数据库的弊端分析:

  • DAO has many many code
  • DAOImpl has many duplicate code
  • Develop the page and other functions
  • ...

Spring Data JPA

  • 开发环境
  • Spring Data JPA HelloWorld开发

Spring Data JPA进阶

Repository接口讲解
  • Repository接口是Spring Data的核心接口,不提供任何方法
  • public interface Repository<T,ID extends Serializable>{}
  • @RepositoryDefinition注解的使用

Repository类的定义(源码):

public interface Repository<T, ID extends Serializable> {
}

1. Repository是一个空接口,标记接口,没有包含方法声明的接口
2. 如果我们定义的接口EmployeeRepository extends Repository
如果我们的接口没有extends Repository,运行时会报错

org.springframework.beans.factory.NoSuchBeanDefinitionException:No qualifying bean of type 'com.imooc.repository.EmployeeRepository' available

3. 添加注解能够达到不用extends Repository的功能

@RepositoryDefinition(domainClass = Employee.class,idClass = Integer.class)


#####Repository的子接口讲解
> * CrudRepository:继承Repository,实现了CRUD相关的方法

public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {

/**
 * Saves a given entity. Use the returned instance for further operations as the save operation might have changed the
 * entity instance completely.
 * 
 * @param entity
 * @return the saved entity
 */
<S extends T> S save(S entity);

/**
 * Saves all given entities.
 * 
 * @param entities
 * @return the saved entities
 * @throws IllegalArgumentException in case the given entity is (@literal null}.
 */
<S extends T> Iterable<S> save(Iterable<S> entities);

/**
 * Retrieves an entity by its id.
 * 
 * @param id must not be {@literal null}.
 * @return the entity with the given id or {@literal null} if none found
 * @throws IllegalArgumentException if {@code id} is {@literal null}
 */
T findOne(ID id);

/**
 * Returns whether an entity with the given id exists.
 * 
 * @param id must not be {@literal null}.
 * @return true if an entity with the given id exists, {@literal false} otherwise
 * @throws IllegalArgumentException if {@code id} is {@literal null}
 */
boolean exists(ID id);

/**
 * Returns all instances of the type.
 * 
 * @return all entities
 */
Iterable<T> findAll();

/**
 * Returns all instances of the type with the given IDs.
 * 
 * @param ids
 * @return
 */
Iterable<T> findAll(Iterable<ID> ids);

/**
 * Returns the number of entities available.
 * 
 * @return the number of entities
 */
long count();

/**
 * Deletes the entity with the given id.
 * 
 * @param id must not be {@literal null}.
 * @throws IllegalArgumentException in case the given {@code id} is {@literal null}
 */
void delete(ID id);

/**
 * Deletes a given entity.
 * 
 * @param entity
 * @throws IllegalArgumentException in case the given entity is (@literal null}.
 */
void delete(T entity);

/**
 * Deletes the given entities.
 * 
 * @param entities
 * @throws IllegalArgumentException in case the given {@link Iterable} is (@literal null}.
 */
void delete(Iterable<? extends T> entities);

/**
 * Deletes all entities managed by the repository.
 */
void deleteAll();

}

> * PagingAndSortingRepository:继承CrudRepository,实现了分页排序相关的方法

public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {

/**
 * Returns all entities sorted by the given options.
 * 
 * @param sort
 * @return all entities sorted by the given options
 */
Iterable<T> findAll(Sort sort);

/**
 * Returns a {@link Page} of entities meeting the paging restriction provided in the {@code Pageable} object.
 * 
 * @param pageable
 * @return a page of entities
 */
Page<T> findAll(Pageable pageable);

}

> * JpaRepository:继承PagingAndSortingRepository,实现JPA规范相关的方法

public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {

/*
 * (non-Javadoc)
 * @see org.springframework.data.repository.CrudRepository#findAll()
 */
List<T> findAll();

/*
 * (non-Javadoc)
 * @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Sort)
 */
List<T> findAll(Sort sort);

/*
 * (non-Javadoc)
 * @see org.springframework.data.repository.CrudRepository#findAll(java.lang.Iterable)
 */
List<T> findAll(Iterable<ID> ids);

/*
 * (non-Javadoc)
 * @see org.springframework.data.repository.CrudRepository#save(java.lang.Iterable)
 */
<S extends T> List<S> save(Iterable<S> entities);

/**
 * Flushes all pending changes to the database.
 */
void flush();

/**
 * Saves an entity and flushes changes instantly.
 * 
 * @param entity
 * @return the saved entity
 */
<S extends T> S saveAndFlush(S entity);

/**
 * Deletes the given entities in a batch which means it will create a single {@link Query}. Assume that we will clear
 * the {@link javax.persistence.EntityManager} after the call.
 * 
 * @param entities
 */
void deleteInBatch(Iterable<T> entities);

/**
 * Deletes all entites in a batch call.
 */
void deleteAllInBatch();

/**
 * Returns a reference to the entity with the given identifier.
 * 
 * @param id must not be {@literal null}.
 * @return a reference to the entity with the given identifier.
 * @see EntityManager#getReference(Class, Object)
 */
T getOne(ID id);

}

#####Repository中查询方法定义规则和使用
> * 了解Spring Data中查询方法名称的定义规则
* 使用Spring Data完成复杂查询方法名称的命名


![Repository中查询方法定义规则和使用](http://upload-images.jianshu.io/upload_images/3831072-c4d28f7294210447.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


![Repository中查询方法定义规则和使用](http://upload-images.jianshu.io/upload_images/3831072-68d927a438c5fd36.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


***beans.xml***

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

<!--1. 配置数据源-->
<bean id = "dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
    <property name="url" value="jdbc:mysql://localhost:3306/spring_data"/>
</bean>

<!--2.配置EntityManager-->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
    </property>
    <property name="packagesToScan" value="com.imooc"/>
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
            <prop key="hibernate.show_sql">true</prop>
            <prop key="hibernate.format_sql">true</prop>
            <prop key="hibernate.hbm2ddl.auto">update</prop>
        </props>
    </property>
</bean>

<!--3.配置事务管理器-->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>

<!--4.支持注解的事务-->
<tx:annotation-driven transaction-manager="transactionManager"/>

<!--5.配置spring data-->
<jpa:repositories base-package="com.imooc" entity-manager-factory-ref="entityManagerFactory"/>

<context:component-scan base-package="com.imooc"/>

</beans>


***Employee.java***

/**

  • 雇员:先开发实体类====>自动生成数据表

  • Created by ZCF on 2017/6/21.
    */
    @Entity
    public class Employee {

    private Integer id;

    private String name;

    private Integer age;

    @GeneratedValue
    @Id
    public Integer getId() {
    return id;
    }

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

    @Column(length = 20)
    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public Integer getAge() {
    return age;
    }

    public void setAge(Integer age) {
    this.age = age;
    }
    }


***EmployeeRepository.java***

@RepositoryDefinition(domainClass = Employee.class,idClass = Integer.class)
public interface EmployeeRepository{

public Employee findByName(String name);

//where name like ?% and age<?
public List<Employee> findByNameStartingWithAndAgeLessThan(String name, Integer age);

//where name like ?% and age<?
public List<Employee> findByNameEndingWithAndAgeLessThan(String name, Integer age);

//where name in(?,?....) or age<?
public List<Employee> findByNameInOrAgeLessThan(List<String> names,Integer age);

//where name in(?,?....) and age<?
public List<Employee> findByNameInAndAgeLessThan(List<String> names,Integer age);

}


***EmployeeRepositoryTest.java***

public class EmployeeRepositoryTest {

private ApplicationContext ctx = null;
private EmployeeRepository employeeRepository = null;

@Before
public void setup(){
    ctx = new ClassPathXmlApplicationContext("beans-new.xml");
    employeeRepository = ctx.getBean(EmployeeRepository.class);
    System.out.println("setup");
}

@After
public void tearDown(){
    ctx = null;
    System.out.println("tearDown");
}

@Test
public void testFindByName(){
    Employee employee = employeeRepository.findByName("zhangsan");
    System.out.println("id:" + employee.getId() + ",name:" + employee.getName() + ",age:" + employee.getAge());
}

@Test
public void testfindByNameStartingWithAndAgeLessThan(){
    List<Employee> employees = employeeRepository.findByNameStartingWithAndAgeLessThan("test",22);
    for(Employee employee:employees){
        System.out.println("id:" + employee.getId() + ",name:" + employee.getName() + ",age:" + employee.getAge());
    }
}

@Test
public void testfindByNameEndingWithAndAgeLessThan(){
    List<Employee> employees = employeeRepository.findByNameEndingWithAndAgeLessThan("6",23);
    for(Employee employee:employees){
        System.out.println("id:" + employee.getId() + ",name:" + employee.getName() + ",age:" + employee.getAge());
    }
}

@Test
public void testfindByNameInOrAgeLessThan(){
    List<String> names = new ArrayList<String>();
    names.add("test1");
    names.add("test2");
    names.add("test3");
    List<Employee> employees = employeeRepository.findByNameInOrAgeLessThan(names,22);
    for(Employee employee:employees){
        System.out.println("id:" + employee.getId() + ",name:" + employee.getName() + ",age:" + employee.getAge());
    }
}

@Test
public void testfindByNameInAndAgeLessThan(){
    List<String> names = new ArrayList<String>();
    names.add("test1");
    names.add("test2");
    names.add("test3");
    List<Employee> employees = employeeRepository.findByNameInAndAgeLessThan(names,22);
    for(Employee employee:employees){
        System.out.println("id:" + employee.getId() + ",name:" + employee.getName() + ",age:" + employee.getAge());
    }
}

}


#####Repository
> * Repository
* RepositoryDefinition
* Repository Query Specifications
* Query Annotation
* Update/Delete/Transaction

#####Repository Hierachy
> * CrudRepository
* PagingAndSortingRespository
* JpaRepository
* JpaSpecificationExecutor


***Spring Data 弊端分析***
> 1. 方法名会比较长:约定大于配置
2. 对于一些复杂的查询,是很难实现

#####Query注解使用
> * 在Repository方法中使用,不需要遵循查询方法命名规则
* 只需要将@Query定义在Repository中的方法之上即可
* 命名参数及索引参数的使用
* 本地查询

@Query("select o from Employee o where id = (select max(id) from Employee t1) ")
public Employee getEmployeeByMaxId();

@Query("select o from Employee o where o.name=?1 and o.age=?2")
public List<Employee> queryParams1(String name,Integer age);

@Query("select o from Employee o where o.name=:name and o.age=:age")
public List<Employee> queryParams2(@Param("name")String name,@Param("age")Integer age);

@Query("select o from Employee o where o.name like %?1%")
public List<Employee> queryLike1(String name);

@Query("select o from Employee o where o.name like %:name%")
public List<Employee> queryLike2(@Param("name") String name);

@Query(nativeQuery = true,value = "select count(1) from employee")
public long getCount();

#####更新及删除操作整合事务的使用
> * @Modifying注解使用
* @Modifying结合@Query注解执行更新操作
* @Transactional在Spring Data中的使用

###事务在Spring data中的使用:
> * 事务一般是在Service层
* @Query、@Modifying、@Transactional的综合使用

***EmployeeRepository.java***
@Modifying
@Query("update Employee o set o.age = :age where o.id = :id")
public void update(@Param("id") Integer id,@Param("age") Integer age);
***EmployeeService .java***

@Service
public class EmployeeService {

@Autowired
private EmployeeRepository employeeRepository;

@Transactional
public void update(Integer id,Integer age){
    employeeRepository.update(id,age);
}

}


###CrudRepository接口使用详解
> * save(entity)
* save(entities)
* findOne(id)
* exists(id)
* findAll()
* delete(id)
* delete(entity)
* delete(entities)
* deleteAll()

***EmployeeCrudRepository .java***

public interface EmployeeCrudRepository extends CrudRepository<Employee,Integer> {
}

***EmployeeService .java***

@Service
public class EmployeeService {

@Autowired
private EmployeeRepository employeeRepository;

@Autowired
private EmployeeCrudRepository employeeCrudRepository;

@Transactional
public void update(Integer id,Integer age){
    employeeRepository.update(id,age);
}

@Transactional
public void save(List<Employee> employees){
    employeeCrudRepository.save(employees);
}

}

####PagingAndSortingRespository接口使用详解
> * 该接口包含分页和排序的功能
* 带排序的查询:findAll(Sort sort)
* 带排序的分页查询:findAll(Pageable pageable)

***EmployeePagingAndSortingRepository .java***

public interface EmployeePagingAndSortingRepository extends PagingAndSortingRepository<Employee,Integer> {
}

***EmployeePagingAndSortingRepositoryTest .java***

public class EmployeePagingAndSortingRepositoryTest {

private ApplicationContext ctx = null;
private EmployeePagingAndSortingRepository employeePagingAndSortingRepository = null;

@Before
public void setup(){
    ctx = new ClassPathXmlApplicationContext("beans-new.xml");
    employeePagingAndSortingRepository = ctx.getBean(EmployeePagingAndSortingRepository.class);
}

@After
public void tearDown(){
    ctx = null;
    System.out.println("tearDown");
}

@Test
public void testPage(){
    //page:index从0开始
    Pageable pageable = new PageRequest(0,5);
    Page<Employee> page = employeePagingAndSortingRepository.findAll(pageable);
    System.out.println(page.getTotalPages() + ":," + page.getNumber());
}

@Test
public void testPageAndSort(){
    Sort.Order order = new Sort.Order(Sort.DEFAULT_DIRECTION.DESC,"id");
    Sort sort = new Sort(order);
    Pageable pageable = new PageRequest(0,5,sort);
    Page<Employee> page = employeePagingAndSortingRepository.findAll(pageable);
    System.out.println(page.getTotalPages() + ":," + page.getNumber());
}

}

####JpaRepository接口使用详解
> * findAll
* findAll(Sort sort)
* save(entities)
* flush
* deleteInBatch(entities)

***EmployeeJpaRepository ***

public interface EmployeeJpaRepository extends JpaRepository<Employee,Integer> {
}

####JpaSpecificationExecutor接口使用详解
> * Specification封装了JPA Criteria查询

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

推荐阅读更多精彩内容