最近在学习持久层,我从jdbc到mybatis到mybatisplus自己上网找了几个demo来模仿学习理解。
JDBC是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,JDBC API 允许用户访问任何形式的表格数据,尤其是存储在关系数据库中的数据。是java程序访问关系数据库最原生的接口。
连接数据源,如:数据库。
为数据库传递查询和更新指令。
处理数据库响应并返回的结果。
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql:///ssm";//所连数据库的URL
String username = "root";//连接对应数据库时使用的账号
String password = "123456";//对应账号的密码
Connection conn = DriverManager.getConnection(url,username,password);
String sql = "insert into ssm.account(id,name,money) values(?,?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
account account = new account();
account.setId(99);account.setMoney(20.01);account.setName("Breeze");
ps.setInt(1,account.getId());
ps.setString(2,account.getName());
ps.setDouble(3,account.getMoney());
ps.executeUpdate();
ps.close();
conn.close();
1.直接访问数据库底层类。执行效率高
2.步骤比较简单,容易理解。
1.SQL语句硬编码在java代码文件中,由于实际应用sql语句经常发生变化。那么变动后就需要啊重新编译部署。
(硬编码:硬编码是将数据直接嵌入到程序或其他可执行对象的源代码中的软件开发实践,与从外部获取数据或在运行时生成数据不同。 硬编码数据通常只能通过编辑源代码和重新编译可执行文件来修改)
2.频繁地创建数据库连接和释放,造成数据库资源浪费,要对异常进行捕捉处理,处理转换数据类型。最后还要正确关闭连接。较为繁琐,效率下降。
3.对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便。
(例如下面的库名表名字段名就硬编码在了java文件里,每次使用要创建关闭全部完成)
String sql = "insert into ssm.account(id,name,money) values(?,?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1,account.getId());
ps.setString(2,account.getName());
ps.setDouble(3,account.getMoney());
ps.executeUpdate();
ps.close();
conn.close();
4.有时进行CRUD操作时需要进行一些判断限定时,拼接sql语句的过程令人十分痛苦。
举个简单的例子:查询操作, select xx from xxx where xxx and/or xxx xxx,访问数据库的时候经常需要加入许多限定,要进行不同条件的判断。如果根据条件去判断就要控制拼接sql语句,过程繁琐,代码难看难读。选择直接分开写的话,又会带来代码冗余的问题。
为了消除jdbc这些问题,mybatis框架他来了。他将sql语句写在xml文件中,支持动态sql,就明显很好地解耦合,解决了硬编码和sql语句拼接的问题。
MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="com/kuang/dao/userMapper.xml"/>
mappers>
configuration>
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
public class User {
private int id; //id
private String name; //姓名
private String pwd; //密码
//构造,有参,无参
//set/get
//toString()
}
import com.kuang.pojo.User;
import java.util.List;
public interface UserMapper {
List<User> selectUser();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.dao.UserMapper">
<select id="selectUser" resultType="com.kuang.pojo.User">
select * from user
</select>
</mapper>
public class MyTest {
@Test
public void selectUser() {
SqlSession session = MybatisUtils.getSession();
//方法一:
//List users = session.selectList("com.kuang.mapper.UserMapper.selectUser");
//方法二:
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> users = mapper.selectUser();
for (User user: users){
System.out.println(user);
}
session.close();
}
}
1.SQL写在XML中,十分方便管理,解除了SQL与程序代码的耦合。业务逻辑和数据访问逻辑分离,系统设计更清晰合理。
2.提供映射标签,支持对象与数据库的orm字段关系映射 。建立映射后,能够将程序中的对象自动持久化到关系数据库中,并且能够像操作对象一样从数据库获取数据。
AccountMapper.xml
<mapper namespace="com.example.dao.AccountMapper">
<select id="getAllAccounts" resultType="com.example.pojo.Account">
select * from account;
select>
<select id="getAccountById" resultType="com.example.pojo.Account" parameterType="int">
select * from account where id = #{id};
select>
mapper>
上述代码对应1、2特性:sql写在xml,与java文件分离,修改后也不需要重新编译了,实现解耦合,建立了映射后。从数据库中select获取数据就自动存储进了定义的pojo对象,因为他们的字段是一一对应的。
3.提供了xml标签,支持动态SQL。在执行时先判断好逻辑,再直接从数据库取结果。减轻数据库负担。动态sql消除了自行拼接sql的痛苦。
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog
<where>
<if test="title != null">
title = #{title}
if>
<if test="author != null">
or author = #{author}
if>
where>
select>
以上代码段表达了从blog表中查询数据,传入一个map(键值对),如果map有title字段,就将map中title字段的值作为查询的限定。
author同理。如果两者都有那么就是从blog中查找所有满足至少两个限定其中一个的数据
经过观察和实际应用后,我们又发现:
1.整个框架还是比较简陋。实际sql代码还是要自己编写。
2.二级缓存机制不佳
解释:二级缓存是保存在Mapper对象中的,比如现在有一张user表,两个Mapper文件,AMapper.xml和BMapper.xml,B修改了user表中的内容,A是感知不到的,那么再从A里查询如果用到了缓存,就是旧的数据。所以就要必须所有增删改查的操作在同一个命名空间下了。
思考:CRUD的sql大体都是比较接近的,项目持久层代码的结构也如此,那么有什么方法能自动生成这种高重复度的代码和项目结构呢?虽然mybatis也有代码生成器MBG可以生成mapper和映射文件,但Mybatis-plus在此基础上还可以生成service和controller。MybatisPlus的BaseMapper已经封装了许多基本的CRUD操作,我们只需要让pojo对应的dao继承BaseMapper就可以进行绝大部分需求下的CRUD,什么事情都帮我们干好,这还不香吗??
Mybatisplus(简称MP),是一个mybatis的增强工具(国人开源),没有对mybatis进行修改,仅仅增加功能!
pom导入依赖
父级依赖
spring-boot-starter-web、spring-boot-starter-test、mybatisplus-spring-boot-starter、mybatis-plus、mysql-connector-java、mybatis-spring-boot-starter、velocity-engine-core、druid-spring-boot-starter、日志(slf4j-api,slf4j-log4j2)、lombok等
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.3.RELEASEversion>
<relativePath >relativePath>
parent>
server:
port: 8080
spring:
datasource:
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=utf8
type: com.alibaba.druid.pool.DruidDataSource
druid:
#初始化连接池大小
initial-size: 5
#配置最小连接数
min-idle: 5
#配置最大连接数
max-active: 20
#配置连接等待超时时间
max-wait: 60000
#配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
time-between-eviction-runs-millis: 60000
#配置一个连接在池中最小生存的时间,单位是毫秒
min-evictable-idle-time-millis: 300000
#测试连接
validation-query: SELECT 1 FROM DUAL
#申请连接的时候检测,建议配置为true,不影响性能,并且保证安全
test-while-idle: true
#获取连接时执行检测,建议关闭,影响性能
test-on-borrow: false
#归还连接时执行检测,建议关闭,影响性能
test-on-return: false
#是否开启PSCache,PSCache对支持游标的数据库性能提升巨大,oracle建议开启,mysql下建议关闭
pool-prepared-statements: true
#开启poolPreparedStatements后生效
max-pool-prepared-statement-per-connection-size: 20
#配置扩展插件,常用的插件有=>stat:监控统计 log4j:日志 wall:防御sql注入
filters: stat,wall,slf4j
#打开mergeSql功能;慢SQL记录
connection-properties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
#配置DruidStatFilter
web-stat-filter:
enabled: true
url-pattern: "/*"
exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
#配置DruidStatViewServlet
stat-view-servlet:
url-pattern: "/druid/*"
#IP白名单(没有配置或者为空,则允许所有访问)
allow:
#IP黑名单 (存在共同时,deny优先于allow)
deny:
# 禁用HTML页面上的“Reset All”功能
reset-enable: false
#登录名
login-username: root
#登录密码
login-password: root
@SpringBootApplication
//配置mapper文件位置
@MapperScan("com.lxg.springboot.dao.mybatisplus.mapper")
public class MybatisPlusApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisPlusApplication.class, args);
}
}
public class CodeGenerator{
/**
* @param args
* @Title: main
* @Description: 根据数据库生成表格
*/
public static void main(String[] args) {
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
//代码存放地址
gc.setOutputDir( "F://code4" );
gc.setFileOverride( true );
// 不需要ActiveRecord 特性的请改为false
gc.setActiveRecord( false );
// XML 二级缓存
gc.setEnableCache( false );
// XML ResultMap
gc.setBaseResultMap( true );
// XML columList
gc.setBaseColumnList( false );
// 自定义文件命名,注意 %s 会自动填充表实体属性!
gc.setControllerName( "%sController" );
gc.setServiceName( "%sService" );
gc.setServiceImplName( "%sServiceImpl" );
gc.setMapperName( "%sMapper" );
gc.setXmlName( "%sMapper" );
mpg.setGlobalConfig( gc );
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setDbType( DbType.MYSQL );
dsc.setDriverName( "com.mysql.jdbc.Driver" );
dsc.setUsername( "root" );
dsc.setPassword( "root" );
dsc.setUrl( "jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL = false&serverTimezone = UTC" );
mpg.setDataSource( dsc );
// 策略配置
StrategyConfig strategy = new StrategyConfig();
// 此处可以修改为您的表前缀
strategy.setTablePrefix( new String[]{
"t_"} );
// 表名生成策略
strategy.setNaming( NamingStrategy.underline_to_camel );
// 需要生成的表
strategy.setInclude( new String[]{
"t_user"} );
strategy.setSuperServiceClass( null );
strategy.setSuperServiceImplClass( null );
strategy.setSuperMapperClass( null );
mpg.setStrategy( strategy );
// 包配置
PackageConfig pc = new PackageConfig();
pc.setParent( "com.lxg.springboot.dao.mybatisplus" );
pc.setController( "controller" );
pc.setService( "service" );
pc.setServiceImpl( "service/serviceImpl" );
pc.setMapper("mapper");
pc.setEntity( "entity" );
pc.setXml( "xml" );
mpg.setPackageInfo( pc );
// 执行生成
mpg.execute();
}
}
执行上面的代码后,就会自动帮助我们构建项目文件和文件夹,以及各文件的内容(代码)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VOg0wWlc-1597636684016)(对JDBC到mybatis到mybatisplus的技术演变思考.assets/20200817000319163.png)]
common包下定义分页工具相关和响应码枚举
common.PageResult.java
@Data
public class PageResult<T> {
/**
* 总记录数
*/
private Long total;
/**
* 每页数据
*/
private List<T> rows;
public PageResult(Long total, List<T> rows) {
this.total = total;
this.rows = rows;
}
}
common.Result.java
/**
* 统一API响应结果封装
*/
public class Result<T> {
private int code;
private String message;
private T data;
public Result setCode(ResultCode resultCode) {
this.code = resultCode.code();
return this;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
public Result setMessage(String message) {
this.message = message;
return this;
}
public T getData() {
return data;
}
public Result setData(T data) {
this.data = data;
return this;
}
private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";
public static Result success() {
return new Result()
.setCode(ResultCode.SUCCESS)
.setMessage(DEFAULT_SUCCESS_MESSAGE);
}
public static <T> Result<T> success(T data) {
return new Result()
.setCode(ResultCode.SUCCESS)
.setMessage(DEFAULT_SUCCESS_MESSAGE)
.setData(data);
}
public static Result fail(String message) {
return new Result()
.setCode(ResultCode.FAIL)
.setMessage(message);
}
public static Result fail(ResultCode resultCode,String msg) {
return new Result()
.setCode(resultCode)
.setMessage(msg);
}
}
common.StatusCode.java
/**
* 响应码枚举,参考HTTP状态码的语义
*/
public enum StatusCode {
//成功
SUCCESS(200),
//失败
FAIL(400),
//未认证(签名错误)
UNAUTHORIZED(401),
//接口不存在
NOT_FOUND(404),
//服务器内部错误
INTERNAL_SERVER_ERROR(500),
//空指针异常
NULL_POINT(-1),
//类型转换异常
PARSE_EXCEPTION(-1),
//数学运算异常
ARITHMETIC_EXCEPTION(-1),
ILLEGAL_ACCESS_EXCEPTION(-1);
private final int code;
StatusCode(int code) {
this.code = code;
}
public int code() {
return code;
}
}
@EnableTransactionManagement
@Configuration
public class MybatisPlusConfig {
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
/**
* 打印 sql,打印SQL的代码只为了测试开发阶段使用,生产环境需要注释掉,否则日志文件过大。
*/
@Bean
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor performanceInterceptor =new PerformanceInterceptor();
//格式化sql语句
Properties properties =new Properties();
properties.setProperty("format", "false");
performanceInterceptor.setProperties(properties);
return performanceInterceptor;
}
}
@TableName("t_user")
@Data
public class User implements Serializable {
/** id */
@NotBlank(message = "id不能为空")
private String id;
/** 姓名 */
@NotBlank(message = "姓名不能为空")
private String name;
/** 年龄 */
@NotNull(message = "年龄不能为空")
private Integer age;
}
在mapper文件夹下创建实体类对应mapper并继承BaseMapper。mapper文件夹下创建xml文件夹存放mapper映射文件xxxMapper.xml
@Repository
public interface UserMapper extends BaseMapper<User> {
/**
* 方法名与映射文件的id一致
*/
List<User> findByXml();
}
UserMapper.xml
<mapper namespace="com.lxg.springboot.dao.mybatisplus.mapper.UserMapper">
<select id="findByXml" resultType="User">
select * from t_user;
select>
mapper>
service文件夹下创建UserService
public interface UserService {
public void addUser(User user);
public void updateUser(User user);
public void deleteUser(String id) ;
public List<User> findAll();
public User findUserById(String id);
/**
* 条件查询+age排序
* @param
*/
public List<User> findSearch(Map searchMap);
/**
* 条件+分页+age排序
*/
public PageResult<User> findSearch(Map searchMap, int page, int size);
/**
* 通过Xml映射文件查询
*/
public List<User> findByXml();
}
在service文件夹下创建impl文件夹,存放实现类,在impl文件夹下创建UserServiceImpl实现类,实现UserService接口
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public void addUser(User user) {
userMapper.insert(user);
}
@Override
public void updateUser(User user) {
userMapper.updateById(user);
}
@Override
public void deleteUser(String id) {
userMapper.deleteById(id);
}
@Override
public List<User> findAll() {
return userMapper.selectList(null);
}
@Override
public User findUserById(String id) {
return userMapper.selectById(id);
}
@Override
public List<User> findSearch(Map searchMap) {
EntityWrapper<User> wrapper = createSearchCondition(searchMap);
//根据age倒序查询
wrapper.orderBy(true, "age", false);
return userMapper.selectList(wrapper);
}
@Override
public PageResult<User> findSearch(Map searchMap, int page, int size) {
EntityWrapper<User> wrapper = createSearchCondition(searchMap);
//根据age倒序查询
wrapper.orderBy(true, "age", false);
Page<User> userPage = new Page<>(page,size);
List<User> list = userMapper.selectPage(userPage, wrapper);
return new PageResult<>(userPage.getTotal(),list);
}
@Override
public List<User> findByXml() {
return userMapper.findByXml();
}
/**
* 构造查询条件
* @param searchMap
* @return
*/
public EntityWrapper<User> createSearchCondition(Map searchMap) {
EntityWrapper<User> wrapper = new EntityWrapper<>(new User());
if (searchMap.get("name") != null) {
wrapper.eq("name", searchMap.get("name"));
}
if (searchMap.get("age") != null) {
wrapper.eq("age", searchMap.get("age"));
}
return wrapper;
}
}
在controller文件夹下创建UserController
@RestController
@RequestMapping(value = "/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 新增
* @param user
*/
@RequestMapping(method = RequestMethod.POST)
public Result addUser(@RequestBody User user) {
userService.addUser(user);
return new Result(true, StatusCode.OK,"新增成功");
}
/**
* 根据id修改
* @param user
*/
@RequestMapping(method = RequestMethod.PUT)
public Result updateUser(@RequestBody User user) {
if (user.getId() == null || user.getId().equals("")) {
return new Result(false, StatusCode.ERROR,"无id,更新失败");
}
userService.updateUser(user);
return new Result(true, StatusCode.OK,"更新成功");
}
/**
* 根据id删除
* @param id
* @return
*/
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public Result delete(@PathVariable String id) {
userService.deleteUser(id);
return new Result(true, StatusCode.OK,"删除成功");
}
/**
* 查询所有
*/
@RequestMapping(method = RequestMethod.GET)
public List<User> findAllUser() {
return userService.findAll();
}
/**
* 根据id查询
* @param id
*/
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public Result findByUserId(@PathVariable String id) {
return new Result(true, StatusCode.OK,"查询成功",userService.findUserById(id));
}
/**
* 条件查询
*/
@RequestMapping(value="/search",method = RequestMethod.POST)
public Result findSearch(@RequestBody Map searchMap){
return new Result(true,StatusCode.OK,"查询成功 ",userService.findSearch(searchMap));
}
/**
* 条件+分页
* @param searchMap
* @param page
* @param size
*/
@RequestMapping(value = "/search/{page}/{size}",method = RequestMethod.POST)
public Result findSearch(@RequestBody Map searchMap, @PathVariable int page, @PathVariable int size){
return new Result(true,StatusCode.OK,"查询成功",userService.findSearch(searchMap,page,size));
}
/**
* 通过Xml查询成功
* @return
*/
@RequestMapping(value = "/findByXml",method = RequestMethod.GET)
public Result findByXml(){
return new Result(true,StatusCode.OK,"XML查询成功",userService.findByXml());
}
}
对于一般的普通单表,BaseMapper基本够用,复杂单表的较复杂查询可以使用注解。多表联查可以配置xxxMapper.xml
内置通用 Mapper
、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求以上基本的 CRUD 操作,我们仅仅需要继承一个 BaseMapper 即可实现大部分单表 CRUD 操作。BaseMapper 提供了多达 17 个方法供使用, 可以极其方便的实现单一、批量、分页等操作,极大的减少开发负担。但是mybatis-plus的强大不限于此,请看如下需求该如何处理:
支持热加载:Mapper 对应的 XML 支持热加载,对于简单的 CRUD 操作,甚至可以无 XML 启动
支持代码自动生成:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用(P.S. 比 Mybatis 官方的 Generator 更加强大!)
内置分页插件:基于 Mybatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通List查询
Mybatis-Plus实在是太方便了!!