说是SpringData,其实其中包含了太多内容,同样开始看的一头雾水,其实现在还是有很多不了解的地方。

官方文档还是讲的不错的,一开始看会比较迷茫,但是稍微看一些以后,有些疑问在里面有说明。这是地址

这里都是基于SpringBoot的自动配置进行的,所以大部分配置比较简单。

spring jdbc

jdbc是java原有的数据访问组件,创建连接、创建查询、执行查询、结果通过ResultSet逐个读取转换。

当前面加上spring名头后,指的是spring的jdbc框架(或者叫模块?)。通过spring-boot-starter-data-jdbc依赖,使spring程序中可以得到一个注入得jdbctemplate对象,而不再依赖DriverMaanger,而且简化了访问方法。当然底层还是用的jdbc

当然jdbc并非ORM,因为还是面向与sql执行结果得,结果也需要手工转换为实体类(或者通过RowMapper)。

配置

配置比较少,基本使用只要配置DataSource就好了。

其实DataSource并非jdbc的概念,它代表一个数据库的配置,后面SpringJPA也要用到。

同样有代码和文件的配置方式,这是使用文件的方式进行配置的:

<?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 name="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="url"
			value="jdbc:mysql://localhost:3306/testdb?serverTimezone=Asia/Shanghai&characterEncoding=utf8" />
		<property name="username" value="root" />
		<property name="password" value="123456" />
	</bean>
</beans>

对于这个配置文件多说两句:

  1. 网上教程需要配置driver这个property,比如指向mysql,但是我用的时候,会有日志说不用配,会自动加载,我试了的确如此
  2. 注意连接字符串里的serverTimezone=Asia/Shanghai这个配置,可能是jdbc版本原因,如果不加这个,数据库连接会报时区不对的错误。

注意在springboot入口,需要添加对这个配置文件的依赖:

@SpringBootApplication
@ImportResource("classpath:jdbc-config.xml")
public class App {
	public static void main(String[] args) {
		new SpringApplicationBuilder(App.class).run(args);
	}
}

也可以使用代码的方式,两者相同:

@Bean
    public DataSource getDataSource() {
        DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
        dataSourceBuilder.url("jdbc:mysql://localhost:3306/testdb?serverTimezone=Asia/Shanghai");
        dataSourceBuilder.username("root");
        dataSourceBuilder.password("123456");
        return dataSourceBuilder.build();
    }

配置就是这样,很简单

使用jdbcTemplate

repository需要通过注解标记,主要为了

  • 被识别为bean
  • 特殊处理其中抛出的数据库异常
@Repository
public class EmployeeRepository {
	@Autowired
	NamedParameterJdbcTemplate jdbc;

	public void createEmployee(Employee employee) {
		jdbc.execute("insert into employees (name,email) values (:name,:email)",
				new BeanPropertySqlParameterSource(employee), (pc) -> {
					pc.execute();
					return null;
				});
	}

	public void updateEmployee(long id, Employee employee) {
		jdbc.update("update employees set name=:name, email=:email where id=:id", new MapSqlParameterSource()
				.addValue("id", id).addValue("name", employee.getName()).addValue("email", employee.getEmail()));
	}

	public Employee getEmployee(long id) {
		return jdbc.queryForObject("select * from employees where id=:id", new MapSqlParameterSource("id", id),
				new EmployeeRowMapeer());
	}
}

class EmployeeRowMapeer implements RowMapper<Employee> {
	@Override
	public Employee mapRow(ResultSet rs, int rownumber) throws SQLException {
		Employee e = new Employee();
		e.setId(rs.getInt(1));
		e.setName(rs.getString(2));
		e.setEmail(rs.getString(3));
		return e;
	}
}

这里

  1. 注册了NamedParameterJdbcTemplate,相比普通的JdbcTemplate,可以支持MapSqlParameterSourceBeanPropertySqlParameterSource两个类作为结构化参数传入
  2. 使用了update/execute等方法执行数据库操作
  3. 定义了RowMapper来转换返回结果到实体类

spring jpa

前言

有一种说法是spring jpa下面使用了jdbc,不过我没有找到相关的资料。

在spring jpa之前,同样的也有一个JPA,它时操作EnitityManager来进行ORM操作,同样有类似JDBC的复杂性,而SpringJPA封装了这个类到Repository中,使用方便了。

不过在了解SpringJPA前,JPA也有一些概念,比如JPA有一个需要通过EntityManagerFactory创建EntityManager,而EntityManager内部由各种实现(比如hibernate)维护一个Persistance Context,里面跟踪了所有的Entity情况。

spring jpa定义了一套接口规范,具体有一些实现组件,比如spring默认的似乎是hibernate。

SpringJPA在使用上以Repository为基础,有下面这些内容(部分)。

SpringJPA使用spring-boot-starter-data-jpa作为开始依赖。

实体类

  • 通过@Entity定义实体类,所以JPA是一种ORM框架,可以将实体与数据库表关联
@Entity
@Table(name = "employees")
public class Employee {
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	long id;
	String name;
	String email;
  //省略getter setter
}

Repository类

定义了Repository接口,接口有两个模板参数,一个是实体类,一个是实体类的id类型,通过这个信息,找到Repository使用的表。

public interface Repository<T, ID> {
}

直接定义接口名称,Repository可以自动生成实现,比如定义List<Employee> findByName(String name);,就不用写方法实现,JPA内部会自动生成查询方法,并将参数传入

public interface EmployeeRepository extends Repository<Employee, Long> {

	List<Employee> findByName(String name);
}

方法里传入的SortPageable参数也能被正确转换为所需的排序或者分页信息。

预定义的Repository

提供了类似CrudRepository接口,定义了常用的findAll,count,findById等方法;提供了PagingAndSortingRepository,进一步增加了携带SortPageable的相关方法。

应用的Repository集成这些类,常用的CRUD可以不用定义直接使用。

public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {}

使用时可以直接使用save方法:

@Autowired
	EmployeeRepository repo;

	@Override
	public void createEmployee(Employee employee) {
		repo.save(employee);

	}

自定义方法实现:

当通过名称实现不满足要求时,支持JPQLSQL自定义方法。

JPQL

JPQL是JPA定义的SQL,会在Repository初始化的时候就进行预处理,如果其中有错误,这时就会报错。

这是通过JPQL查询的方法:

@Query("select e from #{#entityName} e where name like %?1%")
	List<Employee> CustomQuery(String name, Pageable page);
  • 参数可以通过位置符号?1替代,这里被替换为了name
  • #{#entityName}被自动替代为实体名字,这样不用写死在代码中
  • Pageable参数可以被SpringJPA自动识别并加入到最后的查询中
  • 注意这是一个Like语句,百分号周围没有加单引号,如果加了就得不到想要的结果了,推测应该是ORM框架自动加的
原生sql

同样可以通过原生SQL:

@Query(value = "SELECT * FROM #{#entityName} WHERE name like %:name%", nativeQuery = true)
	List<Employee> NativeQuery(@Param("name") String name);

这里通过nativeQuery标识标明sql被数据库直接执行。
在此之前,同样可以进行参数替换,这里使用了命名参数的方式@Param("name") 对应到sql中的:name

实体名称替换( #{#entityName})同样有效。

同样也需要注意

EntityManager

可以回到java JPA中EntityManager中的方法,对比可以看出来SpringJPA帮忙省略的细节。

注意这里扩展的方法需要实现,所以额外定义一个Cusom接口和Impl类

public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long>, EmployeeRepositoryCustom {

//	@Query(value = "SELECT * FROM #{#entityName} WHERE name like %:name%", nativeQuery = true)
//	List<Employee> NativeQuery(@Param("name") String name);
}

interface EmployeeRepositoryCustom {

	public List<Employee> NativeQuery(String name);
}

class EmployeeRepositoryImpl implements EmployeeRepositoryCustom {

	@PersistenceContext
	EntityManager entityManager;

	public List<Employee> NativeQuery(String name) {
		javax.persistence.Query query = entityManager.createNativeQuery("select * from employees where name like ?1",
				Employee.class);
		query.setParameter(1, name + "%");
		List<Employee> list = query.getResultList();
		return list;
	}
}

相比较之前的Native SQL方式,这里同样是创建原生SQL,只是代码要复杂许多,因为暴露出来的接口EmployeeRepository还需要扩展自自定义的接口EmployeeRepositoryCustom