伪装成mysql的备_如何伪装成一个服务端开发(六) -- 数据库操作

本文介绍了如何在Spring中进行数据库操作,包括JDBC、JPA和JdbcTemplate的使用,以及连接池的配置。详细讲解了Spring Boot与数据库的连接配置,如数据源、JDBC Template、JPA的实现,并演示了如何切换不同的连接池,如Tomcat和DBCP。此外,文章还展示了如何使用JdbcTemplate进行数据查询、插入、更新和删除操作,以及JPA的简单使用。最后提到了MyBatis的配置和使用,包括MyBatis的Mapper接口和映射文件的配置。
摘要由CSDN通过智能技术生成

目录

如何伪装成一个服务端开发(六)

前言

本篇开始学习Spring 的数据库连接。

术语

数据库连接涉及到一些术语,如果在学习之前没有搞清楚,很容易在业务理解上出现偏差。

JDBC : Java DataBase Connectivity ,java数据库连接。 他实际上是一套api标准。

数据源: 是指存储数据的地方,比如mysql oracle ,还有一些内存数据库,比如h2

JPA : java持久层api,在 spring 中jpa的默认实现就是hibernate

JDBCTemplate : 相对比较原始的数据库操作封装,提供了query call 等方法,直接调用sql语句。

连接池: 每一次数据访问请求都必须经历建立数据库连接、打开数据库、存取数据和关闭数据库连接等步骤,而连接并打开数据库是一件既消耗资源又费时的工作,如果频繁发生这种数据库操作,系统的性能必然会急剧下降,甚至会导致系统崩溃。数据库连接池技术是解决这个问题最常用的方法,在许多应用程序服务器(例如:Weblogic,WebSphere,JBoss,Tomcat)中都有默认的实现。当然还有其他三方实现 C3P0 DBCP 等。

依赖

由于h2这种内存数据库使用比较少,所以这里就不分析使用流程了。所以直接学习关系型数据库的使用。

org.springframework.boot

spring-boot-starter-data-jpa

如果要链接mysql,那么就需要添加mysql的链接驱动

mysql

mysql-connector-java

runtime

然后我们可以在application.properties中添加相关链接配置

spring.datasource.url=jdbc:mysql://xxxxxxx:3306/guradz

spring.datasource.username=root

spring.datasource.password=123456

#最大等待连接中的数量, 设 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

首先我们配置了链接地址,username和password。这里我们没有设置连接池,所以spring会自动使用tomcat提供的连接池,然后我们为这个连接池做了一些配置。我们可以使用如下方法查看我们当前使用了哪个连接池。

@Component

public class DataSourceShow implements ApplicationContextAware {

//当这个bean被加载的会后会自动调用这个方法,前面我们有说过。

@Override

public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

DataSource dataSource = applicationContext.getBean(DataSource.class);

System.out.println("--------------------------------");

System.out.println(dataSource.getClass().getName());

System.out.println("--------------------------------");

}

}

通过从IoC容器中获取datasource的bean我们能够知道我们使用的是什么连接池。

这里可以修改我们的连接池,比如要使用dbcp,首先需要引入dbcp连接池的依赖

org.apache.commons

commons-dbcp2

然后修改application.properties中的配置

##数据库连接池初始化连接数

#spring.datasource.tomcat.initial-size=5

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

通过spring.datasource.type来指定连接池。

我的服务器安装的数据库是mariadb 它是mysql的一个分支,所以和mysql的驱动兼容,不过现在mariadb已经有了自己的驱动,所以这里我把驱动换成了mariadb的驱动。

org.mariadb.jdbc

mariadb-java-client

2.3.0

然后略微修改了配置项

#指明了驱动类

spring.datasource.driver-class-name=org.mariadb.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

使用JdbcTemplate

假设我们的数据库中已经存在t_user表,建表sql如下

create table t_user(

id int(12) not null auto_increment,

user_name varchar(60) not null,

/**性别列,1-男,2-女**/

sex int(3) not null default 1 check (sex in(1,2)),

note varchar(256) null,

primary key(id)

);

然后我们在spring项目中添加我们我们自己的User类

//注解不是必须,只是笔者测试需要

@Component

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

public class User {

private Long id = null;

private String userName = null;

private SexEnum sex = null;// 枚举

private String note = null;

.....

}

public enum SexEnum {

MALE(1, "男"),

FEMALE(2, "女");

private int id ;

private String name;

SexEnum(int id, String name) {

this.id = id;

this.name= name;

}

public static SexEnum getEnumById(int id) {

for (SexEnum sex : SexEnum.values()) {

if (sex.getId() == id) {

return sex;

}

}

return null;

}

...

}

然后创建一个jdbctemplate的接口类以及它的实现。

public interface JdbcTmplUserService {

public User getUser(Long id);

public List findUsers(String userName, String note);

public int insertUser(User user);

public int updateUser(User user) ;

public int deleteUser(Long id);

}

@Service

public class JdbcTmplUserServiceImpl implements JdbcTmplUserService {

@Autowired

private JdbcTemplate jdbcTemplate = null;

private RowMapper getUserMapper(){

RowMapper 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 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 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);

}

}

这里有一个叫做RowMapper的东西,他是一个接口,主要用于将数据库返回的Result转化成对应的bean,需要开发者自己实现其中的转化。

至此我们已经具备了我们所有的物料。接下去需要的东西就是调用相关方法链接数据库进行操作了,比如这里还是使用之前打印连接池的方法。

@Component

public class DataSourceShow implements ApplicationContextAware {

@Autowired

private JdbcTmplUserService jdbcTmplUserService;

@Autowired

private User user;

@Override

public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

DataSource dataSource = applicationContext.getBean(DataSource.class);

System.out.println("--------------------------------");

System.out.println(dataSource.getClass().getName());

System.out.println("--------------------------------");

user.setNote("就是要用狂战斧");

user.setSex(SexEnum.FEMALE);

user.setUserName("jdfei");

jdbcTmplUserService.insertUser(user);

}

}

每次运行Application就会调用insertUser插入一个用户数据。

以上就是jdbcTemplate的用法,看上去并无花哨,我们需要使用自己的sql语句,执行我们想要的东西。在android移动开发中DateBaseHelper基本上也是这样,没有过多的封装,一切都是开发者自己来。

jdbcTemplate每次调用query,update等这些命令执行时,都会使用一个单独的数据路连接。如果我么希望在一个连接里面执行多条语句(如果需要执行多条语句,在不同连接中执行,在一定程度上会损失性能和资源),可以使用StatementCallback或者ConnectionCallback

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;

});

}

使用JPA

spring中的JPA依靠了Hibernate。首先需要引入jpa的依赖

org.springframework.boot

spring-boot-starter-data-jpa

对象关系映射

关系对象映射ORM是jpa开发的第一步就是在Bean对象和数据库表之间建了起一个映射关系,比如

// 标明是一个实体类

@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;

/**** setter and getter ****/

public static class SexConverter implements AttributeConverter{

// 将枚举转换为数据库列

@Override

public Integer convertToDatabaseColumn(SexEnum sex) {

return sex.getId();

}

// 将数据库列转换为枚举

@Override

public SexEnum convertToEntityAttribute(Integer id) {

return SexEnum.getEnumById(id);

}

}

}

@Entity表示是一个实体类,用于ORM,@Table指出了映射的数据库表。@Id标注属性为主键。@GeneratedValue对应数据库中的主键策略,GenerationType.IDENTITY表示递增策略。

@Colume表示属性和表列名的映射(如果名字相同可以省略)。

@Convert制定了值之间的转换器

定义jpa接口

一般而言,我们只需要定义 JPA 接口扩展 JpaRepository 便可以获得 JPA 提供的方法。

public interface JpaUserRepository

extends JpaRepository {

}

这并不需要提供任何实现类,这些 Spring 会根据 JPA 接口规范帮我们完成

另外还需要在XXXApplication中添加相关配置

// 定义 JPA 接口扫描包路径

@EnableJpaRepositories(basePackages = "com.guardz.db")

// 定义实体 Bean 扫描包路径

@EntityScan(basePackages = "com.guardz.db")

@SpringBootApplication

public class DbApplication {

public static void main(String[] args) {

SpringApplication.run(DbApplication.class, args);

}

}

MyBatis

mybatis是当前持久层的主流框架,说再多也是空话,还是通过demo直接尝试最为快捷。MyBatis的官方文档有很详细的内容介绍。

首先我们需要引入mybatis的依赖

org.mybatis.spring.boot

mybatis-spring-boot-starter

1.3.2

首先定义User对象

@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() {

}

.......

}

另外,由于需要把sex转换成SexEnum对象,还需要一个TypeHandler接口的实现类(BaseTypeHandler已经帮我们做了很多事情,所以直接继承它更加快捷)。

// 声明 JdbcType 为整型

@MappedJdbcTypes(JdbcType.INTEGER)

// 声明 JavaType 为 SexEnum

@MappedTypes(value=SexEnum.class)

public class SexTypeHandler extends BaseTypeHandler {

// 通过列名读取性别

@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的操作

@Repository

public interface MyBatisUserDao {

public User getUser(Long id);

}

定义的方法很简单,通过id获取User对象。这里使用了@Repository,其实和@Component很像,都拥有注入的功能。但是@Repository一般会用户持久层的bean。

然后需要添加一个映射文件,用于使数据库表能够对应上面定义的Bean。

/p>

PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

select id, user_name as userName, sex, note from t_user where id = #{id}

这里先看到元素的 namespace 属性,指向我们刚才定义的MyBatis操作接口;接着定义一个元素,它代表着一个查询语句,而 id 属性指代这条 SQL 的id,需要和操作接口中对应的方法名保持一致,parameterType 代表参数类型,resultType 指定返回值类型,这里使用了 user,这是一个别名(上面的User上声明了一个别名),也可以使用全限定名(com.guardz.db.User);

再接着是 SQL 语句,搜索结果中列名和 Bean 的属性名是保持一致的。请注意,数据库表中的字段名为 user_name,而 Bean 的属名为 userName,这里的 SQL 是通过字段的别名(userName)来让它们保持一致的。在默认的情况下,MyBatis 会启动自动映射,将 SQL 中的列映射到 Bean 上,有时候你也可以启动驼峰映射,这样就可以不启用别名了。#{id} 代表接口中该方法的参数名

实际上mybatis的使用百分之90%以上都会用到resultMap,用来对Bean属性和列做映射,同时可以直接指定typeHandler(这样可以不在application.properties做配置)。具体用法可以在官网查看。

最后我们还需要在application.properties中配置一些属性。

#MyBatis 映射文件通配

mybatis.mapper-locations=classpath:mapper/userMapper.xml

#MyBatis 扫描别名包,和注解@Alias 联用

mybatis.type-aliases-package=com.guardz.db

#配置 typeHandler 的扫描包

mybatis.type-handlers-package=com.guardz.db

这个时候我的包结构是这样的

2232ca3e554d67fe882064cfa44b2656.png

以上我们具备了所有mybatis的物料,剩下的就是在spring中使用getUser方法了。

我们使用Controller来进行测试

@Controller

@RequestMapping("/mybatis")

public class MyBatisController {

@Autowired

private MyBatisUserService myBatisUserService = null;

@RequestMapping("/getUser")

@ResponseBody

public User getUser(Long id) {

return myBatisUserService.getUser(id);

}

}

当浏览器输入 http://localhost:8080/mybatis/getUser?id=1 时,理论上会搜索id = 1 的user并且返回json字符。

这里我们用到了一个MyBatisUserService来提供能力。

首先定义一个Service接口

public interface MyBatisUserService {

public User getUser(Long id);

}

上面我们定义的控制接口并没有实现,mybatis有几种方法来帮我们自动实现这个类。

使用 MapperFactoryBean 装配 MyBatis 接口

可以在XXXApplication文件中添加如下代码

@Autowired

SqlSessionFactory sqlSessionFactory = null;

// 定义一个 MyBatis 的 Mapper 接口

@Bean

public MapperFactoryBean initMyBatisUserDao() {

MapperFactoryBean bean = new MapperFactoryBean<>();

bean.setMapperInterface(MyBatisUserDao.class);

bean.setSqlSessionFactory(sqlSessionFactory);

return bean;

}

这里的 SqlSessionFactory 是 Spring Boot 自动为我们生成的。

然后实现上面的Service接口

@Service

public class MyBatisUserServiceImpl implements MyBatisUserService {

@Autowired

private MyBatisUserDao myBatisUserDao = null;

@Override

public User getUser(Long id) {

return myBatisUserDao.getUser(id);

}

}

这之后输入http://localhost:8080/mybatis/getUser?id=1  之后就应该能够顺利获得想要的结果了。

使用 MapperScannerConfigurer 扫描装配 MyBatis 接口

使用上面这种写法有个问题,当我们定义了非常多的操作接口(MyBatisUserDao)之后,我们在XXXApplication中就需要添加非常多的方法来生成对应的MapperFactoryBean> 注入到容器中。

我们可以将XXXApplication中的代码替换为以下代码

@Bean

public MapperScannerConfigurer mapperScannerConfig() {

// 定义扫描器实例

MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();

// 加载 SqlSessionFactory,Spring Boot 会自动生成,SqlSessionFactory 实例

mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");

//定义扫描的包

mapperScannerConfigurer.setBasePackage("你的包*");

//限定被标注@Repository 的接口才被扫描

mapperScannerConfigurer.setAnnotationClass(Repository.class);

//通过继承某个接口限制扫描,一般使用不多

//mapperScannerConfigurer.setMarkerInterface(......);

return mapperScannerConfigurer;

}

使用@MapperScan 定义扫描

就像我们能够在通过@ComponentScan指定扫描包一样,我们可以直接通过@MapperScan来添加mybaits的mapper

添加如下注解

@MapperScan(

// 指定扫描包

basePackages = "你的包",

// 指定 SqlSessionFactory,如果 sqlSessionTemplate 被指定,则作废

sqlSessionFactoryRef = "sqlSessionFactory",

// 指定 sqlSessionTemplate,将忽略 sqlSessionFactory 的配置

sqlSessionTemplateRef = "sqlSessionTemplate",

// markerInterface = Class.class,// 限定扫描接口,不常用

annotationClass = Repository.class

)

@MapperScan 允许我们通过扫描加载 MyBaits 的 Mapper,如果你的 Spring Boot 项目中不存在多个 SqlSessionFactory(或者 SqlSessionTemplate),那么你完全可以不配置 sqlSessionFactoryRef(或者 sqlSessionTemplateRef),上述代码关于它们的配置是可有可无的,但是如果是存在多个时,就需要我们指定了,而且有一点是需要注意的:sqlSessionTemplateRef 的优先权是大于 sqlSessionFactoryRef 的,也就是当我们将两者都配置之后,系统会优先选择 sqlSessionTemplateRef,而把 sqlSessionFactoryRef 作废。与我们代码开发一样,制定了扫描的包和注解限定,当然也可以选择接口限定,只是这并不常用。这里我们选择使用注解@Repository 作为限定,这是一个 Spring 对持久层的注解,而事实上 MyBatis 也提供了一个对 Mapper 的注解@Mapper,在工作中我们可以二选其一

MyBatis总结

myBatis的配置项很多,可以在官网找到,当我们遇到比较复杂的配置的时候,可以在applicaiton.properties文件中通过mybatis.config-location 来直接指定一个mybatis文件,这在实际使用中很常见,所以在这里插入一下。

MyBatis是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生Map使用简单的XML或注解,将接口和Java的POJO(Plain Old Java Object,普通的 Java对象)映射成数据库中的记录。

MyBatis 是基于一种 SQL 到 POJO 的模型,它需要我们提供SQL、映射关系(XML 或者注解,目前以 XML 为主)和 POJO。由于没有屏蔽 SQL,这对于追求高响应和性能的互联网系统是十分重要的,因此我们可以尽可能地通过 SQL 去优化性能,也可以做少量的改变以适应灵活多变的互联网应用。与此同时,它还能支持动态 SQL,以适应需求的变化。这样一个灵动的、高性能的持久层框架就呈现在我们面前,这些很符合当前互联网的需要。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值