In front of the word
- Based on Oracle database, this paper explains how to implement CRUD operation of database based on SpringBoot + MyBatis Plus, including the configuration of key features, such as: paging, primary key sequence, timestamp type field automatic filling, etc
Rely on the version
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4. RELEASE</version>
<relativePath/>
</parent>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
Copy the code
Important Configuration Items
spring:
datasource:
url: <jdbc url>
username: <username>
password: <password>
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # Standard output SQL execution log on console
Copy the code
The sample code
Test the database table definition
- the Person table
create table PERSON
(
ID NUMBER not null
constraint PERSON_PK
primary key,
NAME VARCHAR2(100) not null,
CREATED_TIME TIMESTAMP(6) not null,
UPDATED_TIME TIMESTAMP(6))/
Person table primary key sequence
CREATE SEQUENCE PERSON_ID_SEQ
MINVALUE 1
NOMAXVALUE
INCREMENT BY 1
START WITH 1 NOCACHE;
Copy the code
Paging configuration
import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
import com.baomidou.mybatisplus.extension.incrementer.OracleKeyGenerator;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize;
@Configuration
public class MybatisPlusConfig {
/** * Page blocker. */
@Bean
public PaginationInterceptor paginationInterceptor(a) {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
/** * OracleKeyGenerator. */
@Bean
public IKeyGenerator keyGenerator(a) {
return newOracleKeyGenerator(); }}Copy the code
Timestamp type field automatic filling processing class
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
@Component
public class TimestampMetaObjectHandler implements MetaObjectHandler {
private static final String CREATED_TIME_FIELD = "createdTime";
private static final String UPDATED_TIME_FIELD = "updatedTime";
@Override
public void insertFill(MetaObject metaObject) {
this.setInsertFieldValByName(CREATED_TIME_FIELD, new Timestamp(System.currentTimeMillis()), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setUpdateFieldValByName(UPDATED_TIME_FIELD, newTimestamp(System.currentTimeMillis()), metaObject); }}Copy the code
Person entity class
@Data
@Accessors(chain = true)
@TableName(value = "PERSON")
// The default primary key sequence field is of type Long and needs to be explicitly changed
@KeySequence(value = "PERSON_ID_SEQ", clazz = Integer.class)
public class PersonEntity {
@TableId(value = "ID", type = IdType.INPUT)
private Integer id;
@TableField(value = "NAME")
private String name;
// Use the fill attribute to define when to execute the field filling logic
@TableField(value = "CREATED_TIME", fill = FieldFill.INSERT)
private Timestamp createdTime;
// Use the fill attribute to define when to execute the field filling logic
@TableField(value = "UPDATED_TIME", fill = FieldFill.UPDATE)
private Timestamp updatedTime;
}
Copy the code
PersonMapper interface
- Out of the box basic SQL operations are defined in the parent interface BaseMapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
@Mapper
public interface PersonMapper extends BaseMapper<PersonEntity> {}Copy the code
PersonDAO class
- The PersonMapper interface provides richer encapsulation capabilities
- Out of the box SQL extension operations are defined in the parent class ServiceImpl
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@Service
public class PersonDAO extends ServiceImpl<PersonMapper.PersonEntity> {
public IPage<PersonEntity> getPersonListByPage(Integer pageNumber, Integer pageSize) {
// pageNumber Counts from 1
IPage<PersonEntity> page = new Page<>(pageNumber, pageSize);
LambdaQueryWrapper<PersonEntity> queryWrapper = new QueryWrapper<PersonEntity>().lambda().orderByDesc(PersonEntity::getCreatedTime);
returnbaseMapper.selectPage(page, queryWrapper); }}Copy the code
SpringBoot Boot class configuration
@SpringBootApplication
@mapperscan ("
")
public class DemoApplication {... }Copy the code
Integration test class
Insert a single record
@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class)
@Slf4j
// SQL needs to be executed in a transaction
@Transactional
// Disable rollback so that you can see the test effect in the database
@Rollback(value = false)
public class PersonDAOTest {
@Autowired
private PersonMapper personMapper;
@Autowired
private PersonDAO personDAO;
@Test
public void testInsert(a) {
PersonEntity personEntity = new PersonEntity().setName("Alex");
int insert = personMapper.insert(personEntity);
log.info("insert {} record", insert); // }... }Copy the code
MyBatis Plus debug output:
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@33de9d85]
JDBC Connection [io.opentracing.contrib.jdbc.TracingConnection@7e8f97dd] will be managed by Spring
==> Preparing: SELECT PERSON_ID_SEQ.NEXTVAL FROM DUAL
==> Parameters:
<== Columns: NEXTVAL
<== Row: 2
<== Total: 1
==> Preparing: INSERT INTO PERSON ( ID, NAME, CREATED_TIME ) VALUES ( ?, ?, ? )
==> Parameters: 2(Integer), Alex(String), 2021-08-12 11:50:41.309(Timestamp)
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@33de9d85]
Copy the code
You can see:
- Before the INSERT SQL is executed, a SELECT operation is performed on primary key sequence fields
- The CREATED_TIME database field is effectively populated
Update existing records
@Test
public void testUpdate(a) {
PersonEntity personEntity = new PersonEntity().setId(2).setName("AlexLu");
int update = personMapper.updateById(personEntity);
log.info("update {} record", update);
}
Copy the code
MyBatis Plus debug output:
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1f591b2e]
JDBC Connection [io.opentracing.contrib.jdbc.TracingConnection@464017f5] will be managed by Spring
==> Preparing: UPDATE PERSON SET UPDATED_TIME=?, NAME=? WHERE ID=?
==> Parameters: 2021-08-12 11:55:24.762(Timestamp), AlexLu(String), 2(Integer)
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1f591b2e]
Copy the code
You can see:
- The UPDATED_TIME database field is effectively populated
Batch insert record
@Test
public void testSaveBatch(a) {
List<PersonEntity> entityList = new ArrayList<>();
for (int i = 0; i < 2; i++) {
entityList.add(new PersonEntity().setName("Name" + i));
}
boolean result = personDAO.saveBatch(entityList);
log.info("batch insert success: {}", result);
}
Copy the code
- Investigate the difference between the underlying implementation of batch inserts and single inserts
MyBatis Plus debug output:
JDBC Connection [io.opentracing.contrib.jdbc.TracingConnection@2f006edf] will be managed by Spring ==> Preparing: SELECT PERSON_ID_SEQ.NEXTVAL FROM DUAL ==> Parameters: <== Columns: NEXTVAL <== Row: 4 <== Total: 1 ==> Preparing: INSERT INTO PERSON ( ID, NAME, CREATED_TIME ) VALUES ( ? ,? ,?) ==> Parameters: 4(Integer), Name0(String), 2021-08-12 12:02:25.194(Timestamp) ==> Preparing: SELECT PERSON_ID_SEQ.NEXTVAL FROM DUAL ==> Parameters: <== Columns: NEXTVAL <== Row: 5 <== Total: 1 ==> Parameters: 5 (Integer), Name1 (String), the 2021-08-12 12:02:25. 278 (Timestamp)Copy the code
Paging Query Records
@Test
public void testPageQuery(a) {
IPage<PersonEntity> personListByPage = personDAO.getPersonListByPage(1.2);
log.info("total: {}", personListByPage.getTotal());
List<PersonEntity> records = personListByPage.getRecords();
records.forEach(r -> log.info("record: {}", r));
}
Copy the code
MyBatis Plus debug output:
Creating a new SqlSession Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2f006edf] JDBC Connection [io.opentracing.contrib.jdbc.TracingConnection@c6db00d] will be managed by Spring JsqlParserCountOptimize sql=SELECT ID,UPDATED_TIME,NAME,CREATED_TIME FROM PERSON ORDER BY CREATED_TIME DESC ==> Preparing: SELECT COUNT(1) FROM PERSON ==> Parameters: <== Columns: COUNT(1) <== Row: 11 ==> Preparing: SELECT * FROM ( SELECT TMP.*, ROWNUM ROW_ID FROM ( SELECT ID,UPDATED_TIME,NAME,CREATED_TIME FROM PERSON ORDER BY CREATED_TIME DESC ) TMP WHERE ROWNUM <=?) WHERE ROW_ID > ? ==> Parameters: 2(Long), 0(Long) <== Columns: ID, UPDATED_TIME, CREATED_TIME, ROW_ID <== Row: 13, null, Name9, 2021-08-12 12:02:25.848, 1 <== Row: 12, null, Name8, 2021-08-12 12:02:25.777, 2 <== Total: 2 Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2f006edf]Copy the code
It can be seen that:
- Oracle uses the ROWNUM keyword for paging queries. The implementation logic is to identify the ROWNUM boundary where the paging record is located and restrict the query
reference
- MyBatis Plus