mysql数据库复制数据表时的风险

最近在使用redis的缓存技术时,在项目中需要在插入mysql数据表记录的同时,缓存数据到redis。在创建数据表时,为了方便,直接使用复制另外一个数据库中的数据表,结果就悲剧了

package com.springboot.chapter7.service.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.springboot.chapter7.dao.UserDao;
import com.springboot.chapter7.pojo.User;
import com.springboot.chapter7.service.UserService;

/**** imports ****/
@Service
public class UserServiceImpl implements UserService {

	@Autowired
	private UserDao userDao = null;

	// 插入用户,最后MyBatis会回填id,取结果id缓存用户
	@Override
	@Transactional
	@CachePut(value = "redisCache", key = "'redis_user_'+#result.id")
	public User insertUser(User user) {
		userDao.insertUser(user);
		return user;
	}

	// 获取id,取参数id缓存用户
	@Override
	@Transactional
	@Cacheable(value = "redisCache", key = "'redis_user_'+#id")
	public User getUser(Long id) {
		return userDao.getUser(id);
	}

	// 更新数据后,充值缓存,使用condition配置项使得结果返回为null,不缓存
	@Override
	@Transactional
	@CachePut(value = "redisCache", condition = "#result != 'null'", key = "'redis_user_'+#id")
	public User updateUserName(Long id, String userName) {
		// 此处调用getUser方法,该方法缓存注解失效,
		// 所以这里还会执行SQL,将查询到数据库最新数据
		User user = this.getUser(id);
		if (user == null) {
			return null;
		}
		user.setUserName(userName);
		userDao.updateUser(user);
		return user;

	}

	// 命中率低,所以不采用缓存机制
	@Override
	@Transactional
	public List<User> findUsers(String userName, String note) {
		return userDao.findUsers(userName, note);
	}

	// 移除缓存
	@Override
	@Transactional
	@CacheEvict(value = "redisCache", key = "'redis_user_'+#id", beforeInvocation = false)
	public int deleteUser(Long id) {
		return userDao.deleteUser(id);
	}
}

就是insertUser方法时,使用的@CachePut将插入到mysql数据库的t_user数据表的数据,同时缓存到redis

建表:t_user(这里复制spring_boot_chapter10数据库的t_user)

spring_boot_chapter10数据库的t_user的建表语句:

drop table if exists user;
create table t_user(id bigint(100) primary key auto_increment,
user_name varchar(20) default null,note varchar(500) default null)
engine=INNODB,default charset=utf8;

建表后的表结构和数据:

现在要创建spring_boot_chapter7数据库的t_user表,由于数据表结构相同,所以直接使用复制语句:

-- 复制spring_boot_chapter10数据库的t_user表,创建spring_boot_chapter7数据库t_user表,会复制数据表结构和数据。
-- 但是复制的表之后,查看建表语句还是有一些差异(有风险)
drop  table if exists t_user;
create table t_user select * from spring_boot_chapter10.t_user;

 然后查看复制好的数据表:

发现数据结构和表中数据时一样的

现在来测试insertUser()方法,在插入一条语句后

package com.springboot.chapter7.controller;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.springboot.chapter7.pojo.User;
import com.springboot.chapter7.service.UserService;

/****imports ****/
@Controller
@RequestMapping("/user")
public class UserController {
    
    @Autowired
    private UserService userService = null;
    
    @RequestMapping("/getUser")
    @ResponseBody
    public User getUser(Long id) {
        return userService.getUser(id);
    }
    
    @RequestMapping("/insertUser")
    @ResponseBody
    public User insertUser(String userName, String note) {
        User user = new User();
        user.setUserName(userName);
        user.setNote(note);
        userService.insertUser(user);
        return user;
    }
    
    @RequestMapping("/findUsers")
    @ResponseBody
    public List<User> findUsers(String userName, String note) {
        return userService.findUsers(userName, note);
    }
    
    @RequestMapping("/updateUserName")
    @ResponseBody
    public Map<String, Object> updateUserName(Long id, String userName) {
        User user = userService.updateUserName(id, userName);
        boolean flag = user != null;
        String message = flag? "更新成功" : "更新失败";
        return resultMap(flag, message);
    }
    
    @RequestMapping("/deleteUser")
    @ResponseBody
    public Map<String, Object> deleteUser(Long id) {
        int result = userService.deleteUser(id);
        boolean flag = result == 1;
        String message = flag? "删除成功" : "删除失败";
        return resultMap(flag, message);
    }
    
    private Map<String, Object> resultMap(boolean success, String message) {
        Map<String, Object> result = new HashMap<String, Object>();
        result.put("success", success);
        result.put("message", message);
        return result;
    }
}

使用controller测试insertUser()方法,插入数据:

启动sprigboot项目:

浏览器地址输入:http://localhost:8896/user/insertUser?userName==罗山仔&note=高级java工程师

浏览器响应的json数据,返回一个user对象,但是id的值却是null

现在查看mysql数据库:

查看已经成功插入,但是id=0

现在查看redis数据库,看一下是不是也缓存成功:

发现缓存到redis的键(由redis_user_与id拼接的字符串),发现id也是null

那么为什么会这样的?

问题就是出现在复制语句上,复制语句虽然复制了其他数据表的结构和数据,但是查看chapter7数据库的t_user建表语句会发现差异:

可以看到建表语句与chapter10的t_user建表语句不同,复制了结构和数据,但是并不是完全复制的建表语句

chapter7的t_user建表语句中id没有声明为主键,同时自增也没有了,替代成了default 0,所以每次插入数据,id都是0

所以,需要对chapter7的t_user建表语句做一些修改:

然后运行修改语句,加上主键和自增,然后再进行测试,一切正常:

id已经是4了,查看mysql数据库中插入的数据:

而且之前插入的罗山仔的id也有0变成了2

再查看redis

重新缓存了redis_user_4(redis的键),之前的那个键redis_user_null应该是缓存过期了,刷新之后没了,只剩下redis_user_4

ok,就总结到这里,所以复制表时,虽然方便,,但是也要留意一些坑及风险。所以上边用到的直接复制表结构和数据的语句是有风险的,因此如果要直接从其他数据库复制数据表,下边这样使用是安全的(不会没了主键及自增或者没了索引):

-- 使用其他的复制数据表的语句(复制数据表结构),这种复制语句是安全的,建表语句与spring_boot_chapter10.t_user
-- 的一致
create table t_user like spring_boot_chapter10.t_user;

-- 复制表数据
insert into t_user select * FROM spring_boot_chapter10.t_user;

注意:Oracle使用复制语句:

create table t_user  as  select * FROM spring_boot_chapter10.t_user;

也会出现同样的问题,只复制了数据结构和数据,主键和索引等信息不回复制。

发布了74 篇原创文章 · 获赞 20 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/u011174699/article/details/102924512