JDBC --> EJB --> Hibernate --> JPA
MyBatis:一个不屏蔽SQL且提供动态SQL、接口式编程和简易SQL绑定POJO的半自动化框架
Spring知道hiJdbcTemplate的数据库访问模式
依赖于spring-boot-starter-data-jpa后,会默认为配置数据源,这些默认的数据源主要是内存数据库,如h2、hqldb和Derby等内存数据
以h2为例
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>com.h2databasegroupId>
<artifactId>h2artifactId>
<scope>runtimescope>
dependency>
以MySQL为例
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
配置数据库相关信息
application.properties:
spring.datasource.url=jdbc:mysql://localhost:3306/chapter5
spring.datasource.username=root
spring.datasource.password=123456
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#最大等待连接中的数量,设0为没有限制
spring.datasource.tomcat.max-idle=10
#最大连接活动数
spring.datasource.tomcat.max-active=50
#最大等待毫秒数,单位为ms,超过时间会出错误信息
spring.datasource.tomcat.max-wait=10000
#数据库连接池初始化连接数
spring.datasource.tomcat.initial-size=5
使用第三方数据源(如DBCP)
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-dbcp2artifactId>
dependency>
spring.datasource.url=jdbc:mysql://localhost:3306/spring_boot_chapter5
spring.datasource.username=root
spring.datasource.password=123456
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#指定数据库连接池的类型
spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource
#最大等待连接中的数量,设0为没有限制
spring.datasource.dbcp2.max-idle=10
#最大连接活动数
spring.datasource.dbcp2.max-total=50
#最大等待毫秒数,单位为ms,超过时间会出错误信息
spring.datasource.dbcp2.max-wait-millis=10000
#数据库连接池初始化连接数
spring.datasource.dbcp2.initial-size=5
// 用户服务接口
public interface JdbcTmplUserService {
public User getUser(Long id);
public List<User> findUsers(String userName, String note);
public int insertUser(User user);
public int updateUser(User user);
public int deleteUser(Long id);
public User getUser2(Long id);
public User getUser3(Long id);
}
/**** imports ****/
@Service
public class JdbcTmplUserServiceImpl implements JdbcTmplUserService {
@Autowired
private JdbcTemplate jdbcTemplate = null;
// 获取映射关系
private RowMapper<User> getUserMapper() {
// 使用Lambda表达式创建用户映射关系
RowMapper<User> userRowMapper = (ResultSet rs, int rownum)->{
User user = new User();
user.setId(rs.getLong("id"));
user.setUserName(rs.getString("user_name"));
int sexId = rs.getInt("sex");
SexEnum sex = SexEnum.getEnumById(sexId);
user.setSex(sex);
user.setNote(rs.getString("note"));
return user;
};
return userRowMapper;
}
// 获取对象
@Override
public User getUser(Long id) {
// 执行的SQL
String sql = "select id, user_name, sex, note from t_user where id = ?";
// 参数
Object[] params = new Object[] {id};
User user = jdbcTemplate.queryForObject(sql, params, getUserMapper());
return user;
}
// 查询用户列表
@Override
public List<User> findUsers(String userName, String note) {
// 执行的SQL
String sql = "select id, user_name, sex, note from t_user " + "where user_name like concat('%', ?, '%') "
+ "and note like concat('%', ?, '%')";
// 参数
Object[] params = new Object[] {userName, note};
// 使用匿名类实现
List<User> userList = jdbcTemplate.query(sql, params, getUserMapper());
return userList;
}
// 插入数据库
@Override
public int insertUser(User user) {
String sql = "insert into t_user (user_name, sex, note) values(? , ?, ?)";
return jdbcTemplate.update(sql, user.getUserName(), user.getSex().getId(), user.getNote());
}
// 更新数据库
@Override
public int updateUser(User user) {
// 执行的SQL
String sql = "update t_user set user_name = ?, sex = ?, note = ? " + " where id = ?";
return jdbcTemplate.update(sql, user.getUserName(), user.getSex().getId(), user.getNote(), user.getId());
}
// 删除数据
@Override
public int deleteUser(Long id) {
// 执行的SQL
String sql = "delete from t_user where id = ?";
return jdbcTemplate.update(sql, id);
}
public User getUser2(Long id) {
// 通过Lambda表达式使用StatementCallback
User result = this.jdbcTemplate.execute((Statement stmt) -> {
String sql1 = "select count(*) total from t_user where id= " + id;
ResultSet rs1 = stmt.executeQuery(sql1);
while(rs1.next()) {
int total = rs1.getInt("total");
System.out.println(total);
}
// 执行的SQL
String sql2 = "select id, user_name, sex, note from t_user"
+ " where id = " + id;
ResultSet rs2 = stmt.executeQuery(sql2);
User user = null;
while (rs2.next()) {
int rowNum = rs2.getRow();
user = getUserMapper().mapRow(rs2, rowNum);
}
return user;
});
return result;
}
public User getUser3(Long id) {
// 通过Lambda表达式使用ConnectionCallback接口
return this.jdbcTemplate.execute((Connection conn) -> {
String sql1 = " select count(*) as total from t_user"
+ " where id = ?";
PreparedStatement ps1 = conn.prepareStatement(sql1);
ps1.setLong(1, id);
ResultSet rs1 = ps1.executeQuery();
while (rs1.next()) {
System.out.println(rs1.getInt("total"));
}
String sql2 = " select id, user_name, sex, note from t_user "
+ "where id = ?";
PreparedStatement ps2 = conn.prepareStatement(sql2);
ps2.setLong(1, id);
ResultSet rs2 = ps2.executeQuery();
User user = null;
while (rs2.next()) {
int rowNum = rs2.getRow();
user= getUserMapper().mapRow(rs2, rowNum);
}
return user;
});
}
}
对JdbcTemplate的映射关系需要开发者自己实现RowMapper的接口的,这样就可以完成数据库数据到POJO ( Plain Ordinary Java Object )对象的映射
增删改查:主要是传递参数,然后执行SQL后返回影响数据库记录数
JdbcTemplate每调用一次便会生成一个数据库连接
List list = this.jdbcTemplate.query(sql1, rowMapper);
this.jdbcTemplate.update(sql2);
从表面上看,这两个SQL都在同一个逻辑完成,而实际从底层的角度来看,它们是使用不同的数据库连接完成的
在一个连接里面执行多条SQL可以使用StatementCallback或者ConnectionCallback接口实现回调
JPA(Java Persistence API,Java 持久化API),是定义了对象关系映射(ORM)以及实体对象持久化的标准接口,不局限于EJB 3.0,而是作为POJO持久化的标准规范,可以脱离容器独立运行、开发和测试,更加方便
在Spring Boot中JPA是依靠Hibernate实现的
JPA所维护的核心是实体(Entity Bean),而它是通过一个持久化上下文(Persistence Context)来使用的
持久化上下文包含3个部分:
/**** imports ****/
// 标明是一个实体类
@Entity(name = "user")
// 定义映射的表
@Table(name = "t_user")
public class User {
// 标明主键
@Id
// 主键策略,递增
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id = null;
// 定义属性和表的映射关系
@Column(name = "user_name")
private String userName = null;
private String note = null;
// 定义转换器
@Convert(converter = SexConverter.class)
private SexEnum sex = null;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
public SexEnum getSex() {
return sex;
}
public void setSex(SexEnum sex) {
this.sex = sex;
}
}
@Entity:表明这是一个实体类
@Table:属性name支出映射数据库的表
@Id:标明属性为表的主键
@GeneratedValue:配置采取何种策略生成主键
@Column:与数据库表的字段对应
@Convert:指定转换器。定义从数据库读出的转换规则和从属性转换为数据库列的规则
/**** imports ****/
public class SexConverter implements AttributeConverter<SexEnum, Integer>{
// 将枚举转换为数据库列
@Override
public Integer convertToDatabaseColumn(SexEnum sex) {
return sex.getId();
}
// 将数据库列转换为枚举
@Override
public SexEnum convertToEntityAttribute(Integer id) {
return SexEnum.getEnumById(id);
}
}
JpaRepository接口:定义对应操作
一般而言,只需要定义JPA 接口扩展JpaRepository便可以获得JPA提供的方法
public interface JpaUserRepository extends JpaRepository<User, Long> {
}
配置JPA属性
#使用MySQL数据库方言
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
#打印数据库SQL
spring.jpa.show-sql=true
#选择Hibernate数据定义语言(DDL)策略为update
spring.jpa.hibernate.ddl-auto=update
有时可能需要更加灵活的查询
使用JPA查询语言(JPQL)
与Hibernate提供的HQL十分接近
使用注解@Query标识语句
// from user中的user是代码中定义的实体类名称(@Entity注解的name属性),所以才能这样定义一条JPQL,提供给上层调用
@Query("from user where user_name like concat('%', ?1, '%') "
+ "and note like concat('', ?2, '%')")
public List<User> findUsers(String userName, String note);
按照一定规则命名的方法
/**
* 按用户名称模糊查询
* @param userName 用户名
* @return 用户列表
*/
List<User> findByUserNameLike(String userName);
/**
* 根据主键查询
* @param id -- 主键
* @return 用户
*/
User getUserById(Long id);
/**
* 按照用户名称或者备注进行模糊查询
* @param userName 用户名
* @param note 备注
* @return 用户列表
*/
List<User> findByUserNameLikeOrNoteLike(String userName, String note);
JPA还提供了级联等内容
比JPA和Hibernate更为简单易用,也更灵活
官方定义:MyBatis是支持定制化SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis可以对配置和原生Map使用简单的XML或注解,将接口和Java的POJO(Plain Old Java Object,普通的Java对象)映射成数据库中的记录
MyBatis 是基于一种SQL到POJO的模型,它需要我们提供SQL 、映射关系(XML 或者注解, 目前以XML为主)和POJO。但对于SQL和POJO的映射关系,提供了自动映射和驼峰映射等,使开发者的开发工作大大减少;由于没有屏蔽SQL,这对于追求高响应和l性能的互联网系统是十分重要的,因此可以尽可能地通过SQL去优化性能,也可以做少量的改变以适应灵活多变的互联网应用。与此同时, 它还能支持动态SQL,以适应需求的变化。这样一个灵动的、高性能的持久层框架就呈现在我们面前,这些很符合当前互联网的需要
配置文件包括两个大的部分:基础配置文件、映射文件
依赖包:
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>1.3.1version>
dependency>
MyBatis是一个基于SqlSessionFactory构建的框架。对于SqlSessionFactory 而言,它的作用是生成SqISession接口对象,这个接口对象是MyBatis操作的核心
在MyBatis-Spring的结合中甚至可以“擦除”SqISession对象,使其在代码中“消失”,这样做的意义是重大的,因为SqlSession是一个功能性的代码,“擦除”它之后,就剩下了业务代码,这样就可以使得代码更具可读性
因为SqlSessionFactory的作用是单一的,只是为了创建核心接口SqISession,所以在MyBatis应用的生命周期中理当只存在一个SqlSessionFactory对象,并且往往会使用单例模式。而构建SqlSessionFactory是通过配置类(Configuration)来完成的,因此mybatis-spring-boot-starter会给予我们在配置文件(application.properties进行Configuration配置的相关内容
MyBatis可配置的内容:
@Alias(value = "user")// MyBatis指定别名
public class User {
private Long id = null;
private String userName = null;
private String note = null;
// 性别枚举,需要使用typeHandler进行转换
private SexEnum sex = null;
public User() {
}
/**** setter and getter ****/
}
// 性别typeHandler
/**** imports ****/
// 声明JdbcType为整形
@MappedJdbcTypes(JdbcType.INTEGER)
// 声明JavaType为SexEnum
@MappedTypes(value=SexEnum.class)
public class SexTypeHandler extends BaseTypeHandler<SexEnum> {
// 通过列名读取性别
@Override
public SexEnum getNullableResult(ResultSet rs, String col) throws SQLException {
int sex = rs.getInt(col);
if (sex != 1 && sex != 2) {
return null;
}
return SexEnum.getEnumById(sex);
}
// 通过下标读取性别
@Override
public SexEnum getNullableResult(ResultSet rs, int idx) throws SQLException {
int sex = rs.getInt(idx);
if (sex != 1 && sex != 2) {
return null;
}
return SexEnum.getEnumById(sex);
}
// 通过存储过程读取性别
@Override
public SexEnum getNullableResult(CallableStatement cs, int idx) throws SQLException {
int sex = cs.getInt(idx);
if (sex != 1 && sex != 2) {
return null;
}
return SexEnum.getEnumById(sex);
}
// 设置非空性别参数
@Override
public void setNonNullParameter(PreparedStatement ps, int idx, SexEnum sex, JdbcType jdbcType) throws SQLException {
ps.setInt(idx, sex.getId());
}
}
在MyBatis中对于typeHandler的要求是实现TypeHandler接口
为了更加方便也通过抽象类BaseTypeHandler实现了TypeHandler接口,所以这里直接继承抽象类BaseTypeHandler就可以
注解@MappedJdbcTypes声明JdbcType为数据库的整型,@MappedTypes声明JavaType为SexEnum,这样MyBatis就可据此对对应的数据类型进行转换
为了使这个POJO能够与数据库的数据对应,还需要提供一个映射文件
<?xml version="1.0" encoding="UTF-8">
<mapper namespace="com.springboot.chapter5 dao.MyBatisUserDao">
<select id="getUser" parameterType="long" resultType="user">
select id, user_name as userName, sex , note from t_user where id=#{id}
select>
mapper>
mapper元素
为了启用这个映射,还需要一个接口(仅仅是一个接口,并不需要任何实现类)它就是mapper元素的namespace属性定义的MyBatisUserDao
/**** imports ****/
@Repository
public interface MyBatisUserDao {
public User getUser(Long id);
}
注解@Repository
方法getUser和映射文件中定义的查询SQL的id保持一致,参数也是如此
对映射文件、POJO的别名和typeHandler进行配置
#配置映射文件和扫描别名
#MaBatis映射文件统配
mybatis.mapper-locations=classpath:com/springboot/chapter5/mapper/*.xml
#MyBatis扫描别名文件,和注解@Alias连用
mybatis.type-aliases-package=com.springboot.chapter5.pojo
#配置typeHandler的扫描别
mybatis.type-handlers-package=com.springboot.chapter5.typehandler
#日志配置
#logging.level.root=DEBUG
#logging.level.org.springframework=DEBUG
#logging.level.org.org.mybatis=DEBUG
MapperFactoryBean类
针对一个接口配置
// 使用MapperFactoryBean装配MyBatis接口
@Autowired
SqlSessionFactory sqlSessionFactory = null;
// 定义一个MyBatis的Mapper接口
@Bean
public MapperFactoryBean<MyBatisUserDao> initMyBatisUserDao() {
MapperFactoryBean<MyBatisUserDao> bean = new MapperFactoryBean<>();
bean.setMapperinterface(MyBatisUserDao.class);
bean.setSqlSessionFactory(sqlSessionFactory);
return bean ;
}
// 使用MyBatis接口
/**** imports ****/
@Service
public class MyBatisUserServiceImpl implements MyBatisUserService {
@Autowired
private MyBatisUserDao myBatisUserDao = null;
@Override
public User getUser(Long id) {
return myBatisUserDao.getUser(id);
}
}
因为在启动文件中装配了对应的接口,所以可以采用加粗的代码进行依赖注入,把接口注入应用中
MapperScannerConfigurer类
扫描装配,也就是提供扫描装配MyBatis的接口到Spring IoC容器中
/***
* 配置MyBatis接口扫描
* @return 返回扫描器
*/
@Bean
public MapperScannerConfigurer mapperScannerConfig() {
//定义扫描器实例
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
//加载SqlSessionFactory,Spring Boot会自动生产SqlSessionFactory实例
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
//定义扫描的包
mapperScannerConfigurer.setBasePackage("com.springboot.chapter5.*");
//限定被标注@Repository的接口才被扫描
mapperScannerConfigurer.setAnnotationClass(Repository.class);
//通过继承某个接口限制扫描,一般使用不多
//mapperScannerConfigurer.setMarkerInterface(......);
return mapperScannerConfigurer;
}
使用MapperScannerConfigurer类定义了扫描的包,这样程序就会去扫描对应的包
还使用了注解限制,限制被标注为@Repository,这样就可以防止在扫描中被错误装配
也可以使用接口继承的关系限定,但现实中使用得不多,
注解@MapperScan
能够将MyBatis所需的对应接口扫描装配到Spring IoC容器中
/**** imports ****/
//定义Spring Boot扫描包路径
@SpringBootApplication(scanBasePackages = {"com.springboot.chapter5"})
//定义JPA接口扫描包路径
@EnableJpaRepositories(basePackages = "com.springboot.chapter5.dao")
//定义实体Bean扫描包路径
@EntityScan(basePackages = "com.springboot.chapter5.pojo")
@MapperScan(
//指定扫描包
basePackages = "com.springboot.chapter5.*",
//指定SqlSessionFactory,如果sqlSessionTemplate被指定,则作废
sqlSessionFactoryRef = "sqlSessionFactory",
//指定sqlSessionTemplate,将忽略sqlSessionFactory的配置
sqlSessionTemplateRef = "sqlSessionTemplate",
//markerInterface = Class.class,//限定扫描接口,不常用
annotationClass = Mapper.class
)
public class Chapter5Application {
// ......
}
@MapperScan允许通过扫描加载MyBaits的Mapper
如果Spring Boot项目中不存在多个SqlSessionFactory(或者SqlSessionTemplate),那么完全可以不配置sqlSessionFactoryRef (或者sqlSessionTemplateRef) , 上述代码关于它们的配置是可有可无的,但是如果是存在多个时,就需要指定
注意:sqISessionTemplateRef的优先权大于sqlSessionFactoryRef,也就是当两者都配置之后,系统会优先选择sqlSessionTemplateRef,
而把sqISessionFactoryRef作废
这里选择使用注解@Repository作为限定,这是一个Spring对持久层的注解,而事实上MyBatis也提供了一个对Mapper的注解@Mapper,可以二选其一
MyBatis常用配置
#定义Mapper的XML路径
mybatis.mapper-locations=......
#定义别名扫描的包,需要与@Alias联合使用
mybatis.type-aliases-package=......
#MyBatis配置文件,当配置比较复杂的时候,可以使用它
mybatis.config-location=......
#配置MyBaits插件(拦截器)
mybatis.configuration.interceptors=......
#具体类需要与@MappedJdbcTypes联合使用
mybatis.type-handlers-package=......
#级联延迟加载属性配置
mybatis.configuration.aggressive-lazy-loading=......
#执行器(Executor),可以配置SIMPLE,REUSE,BATCH,默认为SIMPLE
mybatis.executor-type=......
Spring Boot集成MyBatis插件
/**** imports ****/
// 定义拦截签名
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class MyPlugin implements Interceptor {
Properties properties = null;
// 拦截方法逻辑
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("插件拦截方法......");
return invocation.proceed();
}
// 生成MyBatis拦截器代理对象
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
// 设置插件属性
@Override
public void setProperties(Properties properties) {
this.properties = properties;
}
}
通过application.properties文件配置到MyBatis的配置中
mybatis.config.location=classpath:mybatis/mybatis-config.xml
<configuration>
<mappers>
<mapper resource="com/springboot/chapter5/mapper/User_Mapper.xml"/>
mappers>
configuration>
也可以用代码进行处理
// 使用代码配置MyBatis
/**** imports ****/
// 定义Spring Boot扫描包路径
@SpringBootApplication(scanBasePackages={"com.springboot.chapters"})
// 定义JPA接口扫描包路径
@EnableJpaRepositories(basePackages="co.springboot.chapter5.da o")
// 定义实体Bean扫描包路径
@EntityScan(basePackages="com.springboot.chapter5.pojo")
@MapperScan(
// 指定扫描包
basePackages="com.springboot .chapter5.*",
annotationClass=Repository.class)
public class Chapter5Application {
// SqlSessionFactory对象由Spring Boot自动配置生成
@Autowired
SqlSessionFactory sqlSessionFactory = null;
// 启用Spring Bean 生命周期执行方法, 加入插件
@PostConstruct
publiC void initMyBatis() {
// 插件实例
Interceptor plugin = new MyPlugin();
// 设置插件属性
Properties properties = new Properties() ;
properties.setProperty("keyl", "valuel");
properties.setProperty("key2", "value2");
properties.setProperty("key3", "value3");
plugin.setProperties(properties);
// 在sqlSessionFactory中添加插件
sqlSessionFactory.getConfiguration() .addinterceptor(plugin);
}
// ......
}