Spring Data
- Spring的数据访问哲学
Spring的数据访问异常体系
SQLException
- 提供了挺多的异常
- 数据访问模板化
配置数据源
- 使用JNDI
- 使用数据源连接池
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("Root@@715711877");
dataSource.setUrl("jdbc:mysql:///manage");
return dataSource;
}
- 使用嵌入式数据源
使用profile选择数
@Profile("product")
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:h2:~/test");
return dataSource;
}
@Profile("dev")
@Bean
public DataSource dataSourceDev(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("Root@@715711877");
dataSource.setUrl("jdbc:mysql:///manage");
return dataSource;
}
在Spring 当中使用JDBC
JDBC模板
JdbcDaoSupport
update():执行DML语句。增、删、改语句
queryForMap():查询结果将结果集封装为map集合,将列名作为key,将值作为value 将这条记录封装为一个map集合
- 注意:这个方法查询的结果集长度只能是1
queryForList():查询结果将结果集封装为list集合
- 注意:将每一条记录封装为一个Map集合,再将Map集合装载到List集合中
query():查询结果,将结果封装为JavaBean对象
query的参数:RowMapper
- 一般我们使用BeanPropertyRowMapper实现类。可以完成数据到JavaBean的自动封装
- new BeanPropertyRowMapper<类型>(类型.class)
queryForObject:查询结果,将结果封装为对象
- 一般用于聚合函数的查询
配置模板
@Bean
public JdbcTemplate jdbcTemplate(){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSourceDev());
return jdbcTemplate;
}
- 执行操作
@org.springframework.stereotype.Service
public class Service {
@Autowired
private JdbcTemplate jdbcTemplate;
public void insert(Admin admin){
jdbcTemplate.update("INSERT INTO admin(username,password) VALUES(?,?)",
admin.getUsername(),
admin.getPassword());
}
}
使用Lambda表达式
jdbcTemplate.query("select * from admin",r->{
do{
System.out.println(
r.getString("username")+"||"+r.getString("password")
);
}while (r.next());
});
使用命名参数
public void insert(Admin admin){
jdbcTemplate.update("INSERT INTO admin(username,password) VALUES(:username,:password)",
Map.of("username",admin.getUsername(),
"password",admin.getPassword()));
}
JPA
需要的一些复杂特性
- 延迟加载
- 预先抓取
- 级联
集成 Hibernate
Spring与JAVA持久化API
- 配置实体管理器工厂
@Configuration
@ComponentScan("wang.ismy.spring")
@EnableJpaRepositories(basePackages = "wang.ismy.spring",entityManagerFactoryRef = "entityManagerFactoryBean")
public class Config {
@Bean
public DataSource dataSourceDev(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("Root@@715711877");
dataSource.setUrl("jdbc:mysql:///manage");
return dataSource;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(DataSource dataSource,
JpaVendorAdapter adapter){
LocalContainerEntityManagerFactoryBean bean =
new LocalContainerEntityManagerFactoryBean();
bean.setDataSource(dataSource);
bean.setJpaVendorAdapter(adapter);
bean.setPackagesToScan("wang.ismy.spring");
return bean;
}
@Bean
public JpaVendorAdapter jpaVendorAdapter(){
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setDatabase(Database.MYSQL);
adapter.setGenerateDdl(false);
adapter.setDatabasePlatform("org.hibernate.dialect.MySQL5InnoDBDialect");
return adapter;
}
@Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(EntityManagerFactory bean, DataSource dataSource) {
JpaTransactionManager tm =
new JpaTransactionManager();
tm.setEntityManagerFactory(bean);
tm.setDataSource(dataSource);
return tm;
}
}
<?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:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa
https://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<jpa:repositories base-package="wang.ismy.jms" transaction-manager-ref="transactionManager" entity-manager-factory-ref="entityManagerFactory"/>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="root"/>
<property name="password" value="123"/>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///ssm"/>
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--扫描实体类-->
<property name="packagesToScan" value="wang.ismy.jms"/>
<!--服务提供者-->
<property name="persistenceProvider">
<bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
</property>
<!--服务提供者适配器-->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="generateDdl" value="false" />
<property name="database" value="MYSQL" />
<property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
<property name="showSql" value="true" />
</bean>
</property>
<!--高级特性-->
<property name="jpaDialect">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
</property>
</bean>
</beans>
- 从JNDI中获取实体管理器工厂
编写基于JPA的Repository
- 实体类
@Data
@Entity
@Table(name = "admin")
public class Admin {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String username;
private String password;
}
- Repository类
public interface AdminRepository extends JpaRepository<Admin,Integer> { }
- 使用
adminRepository.findAll();
自定义查询方法
public interface AdminRepository extends JpaRepository<Admin,Integer> {
Admin findbyUsername(String username);
}
- 一些关键词
Keyword | Sample | JPQL |
---|---|---|
And | findByLastnameAndFirstname | ... where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | ... where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstnameIs,findByFirstnameEquals | ... where x.firstname = ?1 |
Between | findByStartDateBetween | ... where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | ... where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | ... where x.age ⇐ ?1 |
GreaterThan | findByAgeGreaterThan | ... where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | ... where x.age >= ?1 |
After | findByStartDateAfter | ... where x.startDate > ?1 |
Before | findByStartDateBefore | ... where x.startDate < ?1 |
IsNull | findByAgeIsNull | ... where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull | ... where x.age not null |
Like | findByFirstnameLike | ... where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | ... where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | ... where x.firstname like ?1 (parameter bound with appended %) |
EndingWith | findByFirstnameEndingWith | ... where x.firstname like ?1 (parameter bound with prepended %) |
Containing | findByFirstnameContaining | ... where x.firstname like ?1 (parameter bound wrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc | ... where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | ... where x.lastname <> ?1 |
In | findByAgeIn(Collection ages) | ... where x.age in ?1 |
NotIn | findByAgeNotIn(Collection age) | ... where x.age not in ?1 |
TRUE | findByActiveTrue() | ... where x.active = true |
FALSE | findByActiveFalse() | ... where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | ... where UPPER(x.firstame) = UPPER(?1) |
- 使用SQL
@Query(value = "SELECT * FROM admin WHERE username = 'admin'",nativeQuery = true)
Admin selfCondition();
- 使用JPQL
@Query("FROM Customer WHERE custName = ?1")
List<Customer> findByJPQL(String name);
// 更新操作
@Query("UPDATE Customer SET custName = ?2 WHERE custId = ?1")
@Modifying
int update(Long id,String name);
动态查询
JpaSpecificationExecutor
Specification
- 示例
Specification<Customer> spec = (Specification<Customer>) (root/*比较的属性*/, query, cb/*查询方式*/) -> {
Path<Object> custName = root.get("custName");
return cb.equal(custName,"老王八");
};
Optional<Customer> one = repository.findOne(spec);
System.out.println(one.get());
- 条件拼接
Specification<Customer> spec = (Specification<Customer>) (root/*比较的属性*/, query, cb/*查询方式*/) -> {
Path<Object> custName = root.get("custName");
Path<Object> custIndustry = root.get("custIndustry");
var p1 = cb.equal(custName,"老王八");
var p2 = cb.equal(custIndustry,"隔壁");
return cb.and(p1,p2);
};
- 模糊查询
Specification<Customer> spec = (Specification<Customer>) (root/*比较的属性*/, query, cb/*查询方式*/) -> {
Path<Object> custName = root.get("custName");
return cb.like(custName.as(String.class),"%老%");
};
repository.findAll(spec).forEach(System.out::println);
- 排序
repository.findAll(spec, new Sort(Sort.Direction.DESC,"custId")).forEach(System.out::println);
- 分页
repository.findAll(PageRequest.of(0,3)).forEach(System.out::println);
Page接口
public interface Page<T> extends Slice<T> {
static <T> Page<T> empty() {
return empty(Pageable.unpaged());
}
static <T> Page<T> empty(Pageable pageable) {
return new PageImpl<>(Collections.emptyList(), pageable, 0);
}
int getTotalPages();
long getTotalElements();
<U> Page<U> map(Function<? super T, ? extends U> converter);
}
多表操作
一对多
- 主表
@OneToMany(targetEntity = LinkMan.class)
@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
private Set<LinkMan> linkMan = new HashSet<>(0);
- 从表
@ManyToOne(targetEntity = Customer.class)
@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
private Customer customer;
- 操作
Customer customer = new Customer();
customer.setCustName("20190908");
LinkMan man = new LinkMan();
man.setLkmName("小婊砸");
man.setCustomer(customer);
customerRepository.save(customer);
linkManRepository.save(man);
- 放弃外键维护
@OneToMany(mappedBy = "customer")
- 级联添加
@OneToMany(mappedBy = "customer",cascade = CascadeType.ALL)
Customer customer = new Customer();
customer.setCustName("20190908");
LinkMan man = new LinkMan();
man.setLkmName("小婊砸");
man.setCustomer(customer);
customer.getLinkMans().add(man);
customerRepository.save(customer);
- 级联删除
Optional<Customer> cus = customerRepository.findById(1L);
customerRepository.delete(cus.get());
多对多
@ManyToMany(targetEntity = Role.class)
@JoinTable(name = "user_role",joinColumns = {@JoinColumn(name = "user_id",referencedColumnName = "user_id")},
inverseJoinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "role_id")})
private Set<Role> roleSet = new HashSet<>();
@ManyToMany(targetEntity = User.class)
@JoinTable(name = "user_role",joinColumns ={@JoinColumn(name = "role_id",referencedColumnName = "role_id")},
inverseJoinColumns = {@JoinColumn(name = "user_id",referencedColumnName = "user_id")})
private Set<User> userSet = new HashSet<>();
User user = new User();
user.setUsername("老王");
Role role = new Role();
role.setRoleName("隔壁");
user.getRoleSet().add(role);
userDao.save(user);
roleDao.save(role);
- 级联
对象导航
public enum FetchType {
LAZY,EAGER
}
缓存数据
启用缓存支持
@Configuration
@ComponentScan("wang.ismy.spring")
@EnableCaching
public class Config {
@Bean
public CacheManager cacheManager(){
return new ConcurrentMapCacheManager();
}
}
- Spring 提供的几个缓存管理器
让方法支持缓存
@Cacheable(value = "find",key = "#id")
public String find(Integer id){
System.out.println("real find");
return "hello world"+id;
}
将值放到缓存当中
/** * 该方法肯定会被执行,但是返回结果会放到缓存当中 */ @CachePut(value = "find",key = "#id") public String put(Integer id){ return "new"+id; }
条件化缓存
- unless : 阻止将对象放入缓存,但是还会进行缓存查找
- condition : 不会进行缓存查找,也不会将结果放入缓存
在id等于10时不会进行缓存
@Cacheable(value = "find",key = "#id",condition = "#id != 10")
public String find(Integer id){
System.out.println("real find");
return "hello world"+id;
}
- 移除缓存
@CacheEvict(value = "find",key = "#id")
public void remove(Integer id){}