精华内容
下载资源
问答
  •  例1:cursor会为空的情况,打印cursor的时候不为空,使用的时候就为空了,原因考虑是,多线程操作数据库导致数据库异常  例2:提示正在尝试打开一个已经被关闭的数据库:在多线程访问数据库的时候会出现这样的...

    1 多线程并发操作数据库会导致数据库异常:

             例1:cursor会为空的情况,打印cursor的时候不为空,使用的时候就为空了,原因考虑是,多线程操作数据库导致数据库异常

             例2:提示正在尝试打开一个已经被关闭的数据库:在多线程访问数据库的时候会出现这样的异常: java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed .或 java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase : 或 java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase:

            解决方法(一)

    当你在多线程中只使用一个SQLiteDatabase的引用时,需要格外注意你SQLiteDataBase.close()调用的时机,因为你是使用的同一个引用,比如在一个线程中当一个Add操作结束后立刻关闭了数据库连接,而另一个现场中正准备执行查询操作,但此时db已经被关闭了,然后就会报异常错误。此时一般有三种解决方案,①简单粗暴给所有的CRUD添加一个 synchronized关键字;②永远不关闭数据库连接,只在最后退出是关闭连接。其实每次执行getWriteableDataBase()或getReadableDatabase()方法时,如果有已经建立的数据库连接则直接返回(例外:如果旧的连接是以只读方式打开的,则会在新建连接成功的前提下,关闭旧连接),所以程序中将始终保持有且只有一个数据库连接(前提是单例),资源消耗的很少。③可以自己进行引用计数,简单示例代码如下:
     
    //打开数据库方法
    public synchronized SQLiteDatabase openDatabase() {
    if (mOpenCounter.incrementAndGet() == 1) {
    // Opening new database
    try { 
    mDatabase = sInstance.getWritableDatabase();
    } catch (Exception e) {      f
    mDatabase = sInstance.getReadableDatabase();
    }
    }
    return mDatabase;
    }
     
    //关闭数据库方法
    public synchronized void closeDatabase() {
    if (mOpenCounter.decrementAndGet() == 0) {
    // Closing database
    mDatabase.close();
    }
     }


    解决方法(二)

    这样的异常信息,Sqlite 自身是不支持多线程同时操作的,下面呢我们给出一个解决方案并列出一些项目中用到的代码。

    我们会用到AtomicInteger,一个提供原子操作的Integer的类。因为Android 依托强大的jdk在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则通过一种线程安全的加减操作接口,由此我们可以做一个DatabaseManager 这样的类,具体代码见下面的代码块:

    public class DatabaseManager {
      private AtomicInteger mOpenCounter = new AtomicInteger();
      private static DatabaseManager instance;  
      private static SQLiteOpenHelper mDatabaseHelper;  
      private SQLiteDatabase mDatabase; 
      public static synchronized void initializeInstance(SQLiteOpenHelper helper) {  
        if (instance == null) {  
          instance = new DatabaseManager();  
          mDatabaseHelper = helper;  
        }  
      }  
      public static synchronized DatabaseManager getInstance(SQLiteOpenHelper helper) {  
        if (instance == null) {  
          initializeInstance(helper);
        }  
        return instance;  
      }  
      public synchronized SQLiteDatabase getWritableDatabase() {  
        if(mOpenCounter.incrementAndGet() == 1) {  
          // Opening new database  
          mDatabase = mDatabaseHelper.getWritableDatabase();  
        }  
        return mDatabase;  
      }  
      public synchronized SQLiteDatabase getReadableDatabase() {  
        if(mOpenCounter.incrementAndGet() == 1) {  
          // Opening new database  
          mDatabase = mDatabaseHelper.getReadableDatabase();  
        }  
        return mDatabase;  
      }  
      public synchronized void closeDatabase() {  
        if(mOpenCounter.decrementAndGet() == 0) {  
          // Closing database  
          mDatabase.close();  
        }  
      }
    

    在我们进行关闭数据库的时候判断

    mOpenCounter.decrementAndGet() == 0 (更新器管理的给定对象的字段的当前值为0)的时候才正式关闭数据库,就不会出现上述异常。
    
    用方式呢,在我们操作数据库逻辑代码中如下使用
    首相要取得
    mDatabaseManager = DatabaseManager.getInstance(mContext);
    对象
    /***
     * 判断表中是否有值
     */
    public boolean isExistTabValus() {
      boolean flag = false;
      SQLiteDatabase db = mDatabaseManager.getReadableDatabase();//获取一个可读的数据库对象
      Cursor curcor = null;
      try {
        curcor = db.rawQuery("select * from tab ", null);
        while (curcor.moveToNext()) {
          if (curcor.getCount() > 0) {
            flag = true;
          }
        }
      } catch (Exception e) {
        Log.e(TAG, "isExistTabValus  error");
      } finally {
        if (curcor != null) {
          curcor.close();
        }
        mDatabaseManager.closeDatabase();//关闭数据库
      }
      return flag;
    }
    

    上面提供一个使用方法,现在项目中使用这种方法关于数据库的操作从未没有出现并发的问题,大家可以尝试一下。


    解决方法(三)

    1 为了防止多线程操作导致访问数据库异常,在数据库管理类中使用同一个handler来管理消息队列。所有的数据库操作都在一个handler里处理来操作唯一一个单例数据库代码如下:

    package com.example.aaviewpager;


    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;


    import android.annotation.SuppressLint;
    import android.os.Handler;
    import android.os.Looper;
    import android.os.Message;
    import android.util.Log;


    import com.example.aaviewpager.DatebaseHandleListener.DbListener;
    import com.org.xdbutils.DbManager;
    import com.org.xdbutils.DbManager.DbOpenListener;
    import com.org.xdbutils.DbManager.DbUpgradeListener;
    import com.org.xdbutils.dbutil;
    import com.org.xdbutils.db.sqlite.WhereBuilder;


    /***
     * Parent test = db.selector(Parent.class).where("id", "in", new int[]{1, 3,
     * 6}).findFirst(); long count = db.selector(Parent.class).where("name", "LIKE",
     * "w%").and("age", ">", 32).count(); List<Parent> testList =
     * db.selector(Parent.class).where("id", "between", new String[]{"1",
     * "5"}).findAll();
     * */
    public class DateBaseManager {
    private static final String DATABASE_NAME = "conference.db";
    private static DateBaseManager instance;
    private static DbManager db;


    private DateBaseManager() {
    db = getDb();
    initThread();
    };


    public static DateBaseManager getInstance() {
    if (instance == null) {
    instance = new DateBaseManager();
    }
    if (db == null) {
    db = getDb();
    }
    return instance;
    }


    /**
     * 获取 xutils �?db 模块
     * 
     * @return
     */
    private static DbManager getDb() {
    final DbManager.DaoConfig config = new DbManager.DaoConfig();
    config.setDbName(DATABASE_NAME);
    // config.setDbDir(LogFileUtil.getDiskCacheDir(BaseApplication.getBaseApplication(), "database"));// 设置数据库目�?不设置默认在
    // app
    // 的私有目录中;
    config.setDbVersion(2);
    config.setDbOpenListener(new DbOpenListener() {


    @SuppressLint("NewApi")
    @Override
    public void onDbOpened(DbManager db) {
    // TODO Auto-generated method stub
    db.getDatabase().enableWriteAheadLogging();// �?��wal, 提高写入速度
    }
    });
    config.setDbUpgradeListener(new DbUpgradeListener() {

    @Override
    public void onUpgrade(DbManager db, int oldVersion, int newVersion) {
    // TODO Auto-generated method stub


    }
    });
    return dbutil.getDb(config);
    }



    public void close() {


    try {
    if (db != null) {
    db.close();
    }
    } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    uninitSynchronizeHandler();
    if (instance != null) {
    instance = null;
    }


    }


    private Handler dbHandler;
    private Thread dbThread;


    /**
     * 该方法初始化同步数据的线程�?如果参数为空,则方法会创建一个默认线程来处理数据的同步�? 该方法只会在第一次调用时有效�?  * 
     * @param looper
     */
    private void initThread() {
    if (dbHandler != null) {
    return;
    }
    dbThread = new DBThread();
    dbThread.setName("DBThread");
    dbThread.start();
    }


    /**
     * 该方法在程序�?��时被调用,且必须被调用,否则可能会出现内容泄漏的问题�?  */
    public void uninitSynchronizeHandler() {
    if (dbHandler != null) {
    dbHandler.getLooper().quit();
    dbHandler = null;
    }
    if (dbThread != null) {
    // if(synchronizeThread.isAlive()){
    // synchronizeThread.destroy();
    // }
    }
    }


    private void sendDbMessage(Message msg) {
    if (dbHandler == null) {
    initHandler();
    }
    dbHandler.sendMessage(msg);
    }


    private final static int Msg_updateModle = 0x001;
    private final static int Msg_deleteModle = 0x002;
    private final static int Msg_deleteAllModle = 0x003;
    private final static int Msg_queryModel = 0x004;


    private void initHandler() {
    dbHandler = new Handler(Looper.myLooper()) {


    @Override
    public void handleMessage(Message msg) {
    // TODO Auto-generated method stub
    MessageParams params=null;
    switch (msg.what) {


    case Msg_updateModle:
    params =(MessageParams) msg.obj;
    if (params == null || params.handle == null){
    if(params==null)
    System.out.println("------dbManager Msg_updateModle null return:params==null");
    else
    System.out.println("------dbManager Msg_updateModle null return:params.handle==null");
    return;
    }
    update(params.handle, params.listener);
    break;
    case Msg_deleteModle:
    params =(MessageParams) msg.obj;
    if (params == null || params.handle == null)
    return;
    delete(params.handle, params.listener);
    break;
    case Msg_deleteAllModle:
    params =(MessageParams) msg.obj;
    if (params == null || params.handle == null)
    return;
    deleteAll(params.handle, params.listener);
    break;
    case Msg_queryModel:
    params =(MessageParams) msg.obj;
    if (params == null || params.handle == null){
    if(params==null)
    Log.d("db","------dbManager Msg_queryModel null return:params==null");
    else
    Log.d("db","------dbManager Msg_queryModel null return:params.handle==null");
    return;
    }
    query(params.handle, params.build,params.listener);
    break;
    default:
    break;
    }
    }
    };
    }


    public class DBThread extends Thread {


    @Override
    public void run() {
    // TODO Auto-generated method stub
    // super.run();
    Looper.prepare();
    initHandler();
    Looper.loop();
    }
    }


    public void updateObject(DatebaseHandleListener<?> conctoll, DbListener listener) {
    if(conctoll==null)
    System.out.println("-------dbmanger:updateHadle handle ==null");
    MessageParams params = new MessageParams(conctoll, listener);
    Message msg = Message.obtain();
    msg.obj = params;
    msg.what = Msg_updateModle;
    sendDbMessage(msg);
    }


    private void update(DatebaseHandleListener<?> conctoll, DbListener listener) {
    conctoll.updateObject(db,listener);
    }


    public void dleleteObject(DatebaseHandleListener<?> handle, DbListener listener) {
    MessageParams params = new MessageParams(handle, listener);
    Message msg = Message.obtain();
    msg.obj = params;
    msg.what = Msg_deleteModle;
    sendDbMessage(msg);
    }
    private void delete(DatebaseHandleListener<?> conctoll, DbListener listener) {
    conctoll.deleteObject(db,listener);
    }

    public void deleteAllHandle(DatebaseHandleListener<?> handle, DbListener listener) {
    MessageParams params = new MessageParams(handle, listener);
    Message msg = Message.obtain();
    msg.obj = params;
    msg.what = Msg_deleteAllModle;
    sendDbMessage(msg);
    }


    private void deleteAll(DatebaseHandleListener<?> conctoll, DbListener listener) {
    conctoll.deleteAll(db,listener);
    }

    public void queryObject(DatebaseHandleListener<?> handle,WhereBuilder build, DbListener listener) {
    MessageParams params = new MessageParams(handle,build, listener);
    Message msg = Message.obtain();
    msg.obj = params;
    msg.what = Msg_queryModel;
    sendDbMessage(msg);
    }

    private void query(DatebaseHandleListener<?> conctoll,WhereBuilder b, DbListener listener) {
    conctoll.select(db, b, listener);
    }





    private List<DbListener> dbListeners;


    public void addListener(DbListener listener) {
    if (dbListeners == null) {
    dbListeners = new ArrayList<DbListener>();
    }
    if (!dbListeners.contains(listener)) {
    dbListeners.add(listener);
    }
    }


    public void removeListener(DbListener listener) {
    if (dbListeners == null) {
    return;
    }
    if (dbListeners.contains(listener)) {
    dbListeners.remove(listener);
    }
    }




    class MessageParams {
    DbListener listener;
    DatebaseHandleListener<?> handle;
    WhereBuilder build;
    public MessageParams(DatebaseHandleListener<?> handle, DbListener listener) {
    this.handle = handle;
    this.listener = listener;
    }
    public MessageParams(DatebaseHandleListener<?> handle,WhereBuilder b, DbListener listener) {
    this.handle = handle;
    this.listener = listener;
    this.build = b;
    }
    }
    }


    2 定义一个数据库操作方法的接口类


    package com.example.aaviewpager;


    import java.util.List;


    import com.org.xdbutils.DbManager;
    import com.org.xdbutils.db.sqlite.WhereBuilder;


    /**
     * 数据库操作接�? * 
     * @param <T>
     */
    public interface DatebaseHandleListener<T> {


    boolean updateObject(DbManager db, DbListener listener);


    boolean deleteObject(DbManager db, DbListener listener);


    boolean deleteAll(DbManager db, DbListener listener);


    T selectSingle(DbManager db, WhereBuilder b, DbListener listener);


    List<T> select(DbManager db, WhereBuilder b, DbListener listener);


    // 数据回调接口管理
    public interface DbListener {
    /****数据库操作回*/
    void getResult(int cmd, boolean isSuccess, Object object);
    }


    }

    3 声明一个实体类JavaBean来实现数据库操作方法的接口类 代码如下:


      package com.example.aaviewpager;


    import java.util.List;


    import com.nucomtech.conference.model.dbmodel.DatabaseModleCmd;
    import com.org.xdbutils.DbManager;
    import com.org.xdbutils.db.annotation.Column;
    import com.org.xdbutils.db.annotation.Table;
    import com.org.xdbutils.db.sqlite.WhereBuilder;
    import com.org.xdbutils.ex.DbException;


    @Table(name = "loginInfo")
    public class LoginUserModel implements DatebaseHandleListener<LoginUserModel>{


    @Column(name = "_id",isId = true)
    private long _id;
    @Column(name = "name")
    private String name;
    @Column(name = "password")
    private String password;

    public LoginUserModel(){

    }
    public long get_id() {
    return _id;
    }


    public void set_id(long _id) {
    this._id = _id;
    }


    public String getName() {
    return name;
    }


    public void setName(String name) {
    this.name = name;
    }


    public String getPassword() {
    return password;
    }


    public void setPassword(String password) {
    this.password = password;
    }


    @Override
    public boolean updateObject(DbManager db,
    com.example.aaviewpager.DatebaseHandleListener.DbListener listener){
    // TODO Auto-generated method stub
    // return false;
    try {
    db.selector(LoginUserModel.class).where("name", "=", 123);

    db.saveOrUpdate(this);
    listenerResult(listener,DatabaseModleCmd.LoginUserModle_saveOrUpdate, true,this);
    db.save(this);
    return true;
    } catch (DbException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    return true;
    }


    private void listenerResult(
    com.example.aaviewpager.DatebaseHandleListener.DbListener listener,
    int cmd, boolean isSuccess,
    LoginUserModel model) {
    // TODO Auto-generated method stub
    if(listener == null){
    return;
    }
    listener.getResult(cmd, isSuccess, model);
    }
    @Override
    public boolean deleteObject(DbManager db,
    com.example.aaviewpager.DatebaseHandleListener.DbListener listener) {
    // TODO Auto-generated method stub
    return false;
    }


    @Override
    public boolean deleteAll(DbManager db,
    com.example.aaviewpager.DatebaseHandleListener.DbListener listener) {
    // TODO Auto-generated method stub
    return false;
    }


    @Override
    public LoginUserModel selectSingle(DbManager db, WhereBuilder b,
    com.example.aaviewpager.DatebaseHandleListener.DbListener listener) {
    // TODO Auto-generated method stub
    return null;
    }


    @Override
    public List<LoginUserModel> select(DbManager db, WhereBuilder b,
    com.example.aaviewpager.DatebaseHandleListener.DbListener listener) {
    // TODO Auto-generated method stub
    return null;
    }
    @Override
    public String toString() {
    return "LoginUserModel [_id=" + _id + ", name=" + name + ", password="
    + password + "]";
    }




    }

    4 在Activity中声明javaBean实体类的对象(修改的属性new成对象作为访问数据库的参数),

    package com.example.aaviewpager;


    import java.util.ArrayList;
    import java.util.List;


    import com.example.aaviewpager.DatebaseHandleListener.DbListener;
    import com.nucomtech.conference.model.dbmodel.DatabaseModleCmd;
    import com.nucomtech.conference.model.dbmodel.LoginUserModel;
    import com.nucomtech.conference.serviceproxy.xmppproxy.MessageState;
    import com.nucomtech.conference.view.iview.ILoginView;


    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.os.SystemClock;
    import android.app.ActionBar.LayoutParams;
    import android.app.Activity;
    import android.support.v4.view.ViewPager;
    import android.support.v4.view.ViewPager.OnPageChangeListener;
    import android.view.Menu;
    import android.view.View;
    import android.view.Window;
    import android.widget.ImageView;
    import android.widget.LinearLayout;


    public class MainActivity extends Activity {


    private ViewPager mVp;
    private LinearLayout ll_points;
    private int preposition = 0;
    private int[] images;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);


    LoginUserModel loginUserModel = new LoginUserModel();
    loginUserModel.set_id(123456);
    DateBaseManager.getInstance().updateObject(loginUserModel, new DbListener() {

    @Override
    public void getResult(int cmd, boolean isSuccess, Object object) {
    // TODO Auto-generated method stub
    if (cmd == DatabaseModleCmd.LoginUserModle_saveOrUpdate) {


    } else if (cmd == DatabaseModleCmd.LoginUserModle_select) {
    if (isSuccess) {
    LoginUserModel user = (LoginUserModel) object;

    //   数据库访问成功后做一些其它的操作
    // if (user != null)
    // getView().onLoginResult(ILoginView.Type_loadAccount, MessageState.State_Success,
    // user.getUserName());
    }
    }
    }
    });


    }

    }



    数据库升级的意义

    我们在开发Android应用的时候,不可避免地要使用数据库。而数据库的结构在第一版的时候定下来,之后发布功能更新,或增加业务逻辑,原来的数据库结构可能就不适用了。而如果数据库的结构与之前版本的结构不同,新版本的应用读取旧数据库肯定会出问题。解决办法只有两种:

    1.让用户卸载老版本再安装新的程序;

    2.软件自行更新数据库结构。

    第一种办法很明显不具备可操作性,而且用户一旦卸载软件,数据就丢失了,这是不能容忍的事情。因此,作为开发者必须妥善处理数据库的升级问题。

    当然了,有的同学会说,这问题没意义嘛。我们在设计软件的时候就把数据库设计得完备一点就好了,一开始就考虑周全,以后再也不用管升级的事情。这种方法理论上虽然可行,但实际操作起来是非常困难的,除非你在开发定制软件(例如某些和硬件结合的产品,其硬件发布之后就不再更新或很少更新,这样的软件确实没多大改动)。对于面向市场的应用来说,很可能在立项之初根本不会知道以后会增加哪些功能。这样,我们终究还是要面对这个问题。

    保留数据的升级

    现在以一个假想的程序数据库来谈如何保留原有的数据库进行升级。为直观起见,这里在每次软件版本安装之后导出了数据库文件,并使用SQLite Studio显示表结构和内容,过程从略。并且为了代码的简洁,省略了一些异常处理。我们假设数据库有这样三个版本:

    v1:t_user(username, password);

    v2: t_user(username, password),  t_region(region, code);

    v3: t_user(username, password),  t_region(region, code, country);

    可以看出,第一次升级增加了一张表,而第二次升级修改了表的定义。

    创建和升级数据表的基本知识

    我们在使用数据库之前,基本上会自定义一个类继承自SQLiteOpenHelper。该类的其中一个构造函数形式是这样的(另一个多出来一个DatabaseErrorHandler):

        public SQLiteOpenHelper(Context context, String name, 
                        CursorFactory factory, int version) {
            this(context, name, factory, version, null);
        }

    这个构造函数里面的version参数即是我们设定的版本号。第一次使用数据库时传递的这个版本将被系统记录,并调用SQLiteOpenHelper#onCreate()方法进行建表操作。后续传入的版本如果比这个高,则会调用SQLiteOpenHelper#onUpgrade()方法进行升级。

    增加一张表

    很明显,增加新表(或者删除没有外键的表)的操作代价很小,只需要在onUpgrade()中写好建表操作和插入初始数据就好了。

      public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        if (oldVersion==1){
          db.execSQL("CREATE TABLE t_region(_id integer primary key"
           + "autoincrement, region varchar, code varchar)");
          //insert data...
        }
      }

    wKioL1PjS46A0YztAAEsNiHN2ns849.jpg

    wKioL1PjQQvCoE7JAAEoZeplm10837.jpg

    从上面的图里可以看到,新版本的数据库中已经有t_region表了。

    修改表定义

    SQLite数库对ALTER TABLE命令支持非常有限,只能在表末尾添加列,不能修改列定义,不能删除已有的列。那么如果要修改表呢?我们可以采用临时表的办法。具体来说有四步:

    1. 将现有表重命名为临时表;

    2. 创建新表;

    3. 将临时表的数据导入新表(注意处理修改的列);

    4. 删除临时表。

    以例子中的v2升级到v3为例:

      public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        if (oldVersion==2){
          db.execSQL("ALTER TABLE t_region RENAME TO t_region_temp");
          db.execSQL("CREATE TABLE t_region(_id integer primary key"
               + "autoincrement, region varchar, code varchar, "
               + "country varchar)");
          db.execSQL("insert into t_region(_id, region, code, country) " 
              + "select _id, region, code, \"CHINA\" from t_region_temp");
          db.execSQL("DROP TABLE t_region_temp");
        }
      }

    wKioL1PjRYui_CqZAAExr2IApJw742.jpg

    wKioL1PjRYvzqrd-AAElIqNNLCI543.jpg

    需要注意:

    • 重命名表的SQL格式为ALTER TABLE <oldname> RENAME TO <newname>;

    • 导入新数据的insert into select语句中 不能出现values关键字 ;

    • 记得删除临时表 

    跨越版本的升级

    处理好了单个版本的升级,还有一个更加棘手的问题:如果应用程序发布了多个版本,以致出现了三个以上数据库版本, 如何确保所有的用户升级应用后数据库都能用呢?有两种方式:

    方式一:确定 相邻版本 的差别,从版本1开始依次迭代更新,先执行v1到v2,再v2到v3……

    方式二:为 每个版本 确定与现在数据库的差别,为每个case撰写专门的升级代码。

    方式一的优点是每次更新数据库的时候只需要在onUpgrade方法的末尾加一段从上个版本升级到新版本的代码,易于理解和维护,缺点是当版本变多之后,多次迭代升级可能需要花费不少时间,增加用户等待;

    方式二的优点则是可以保证每个版本的用户都可以在消耗最少的时间升级到最新的数据库而无需做无用的数据多次转存,缺点是强迫开发者记忆所有版本数据库的完整结构,且每次升级时onUpgrade方法都必须全部重写。

    以上简单分析了两种方案的优缺点,它们可以说在花费时间上是刚好相反的,至于如何取舍,可能还需要结合具体情况分析。


          

    展开全文
  • 问题描述:在Controller中使用@Autowired注入的db对象,可以操作数据库,但是在多线程中注入的dbnull,导致一直指针异常 解决办法: 1.用单例模式创建一个数据库辅助类: public class DBHelper { ...

    问题描述:在Controller中使用@Autowired注入的db对象,可以操作数据库,但是在多线程中注入的db为null,导致一直空指针异常

     

    解决办法:

    1.用单例模式创建一个数据库辅助类:

    public class DBHelper {
    
        private static DB db=null;
    
        public DBHelper(DB db) {
            DBHelper.db=db;
        }
        private DBHelper(){
    
        }
    
        /**
         * 用于处理多线程中采集到的数据出现空指针情况
         */
        public static DB getDB(){
            return db;
        }
    
    }

    2.在Controller中使用数据库辅助类的构造函数将db传入:

    //传递db对象
    DBHelper dbHelper=new DBHelper(db);

    3.最后在你引用的地方调用这个db:

    DB db=DBHelper.getDB();
    db.saveAndFlush(arg);

    就ok了!

    转载于:https://www.cnblogs.com/huhu1203/p/8046841.html

    展开全文
  • 关于postgresql数据库PQexec函数,在线程调用执行时,返回值有时为空,且检查结果状态的函数返回也为空,但是命令是执行成功的,数据库里面能查到数据,怎么避免这种情况的发生??还有就是什么原因可能导致...
  • 所谓的缓存穿透,简单来讲就是查询某些不存在的key时,缓存和数据库查询结果都为空,而空的结果又不被缓存起来,而导致每次查询都去请求数据库层的情况。如果接口的并发足够大,那么同时有N多线程直接访问数据库的...

    缓存穿透:
    所谓的缓存穿透,简单来讲就是查询某些不存在的key时,缓存和数据库查询结果都为空,而空的结果又不被缓存起来,而导致每次查询都去请求数据库层的情况。如果接口的并发足够大,那么同时有N多线程直接访问数据库的压力可想而知。
    解决思路:
    如果缓存未命中,那么只有一个线程访问数据库。示例代码如下:

    package com.primer.demo.util;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.ConcurrentMap;
    import java.util.concurrent.FutureTask;
    
    public class CacheTask {  
        private static final ConcurrentMap<String, FutureTask<Object>> cache = new ConcurrentHashMap<String, FutureTask<Object>>();  
        public static Object getInTask(String cacheKey, Callable<Object> caller) {  
            System.out.println("1.缓存未命中,将查询数据库或者调用远程服务");  
            //未命中缓存,开始计算  
            FutureTask<Object> f = cache.get(cacheKey);  
            if (f == null) {  
                FutureTask<Object> ft = new FutureTask<Object>(caller);  
                f = cache.putIfAbsent(cacheKey, ft);  
                if (f == null) {  
                    System.out.println("2.任务未命中,将查询数据库或者调用远程服务");  
                    f = ft;  
                    ft.run();  
                }  
            }  
            else {  
                System.out.println("2.任务命中,直接从缓存取结果");  
            }  
            try {  
                Object result = f.get();  
                System.out.println("取回的结果result:"+result);  
                return result;  
            } catch (Exception e) {  
                e.printStackTrace();  
            }  
            finally{  
                //最后将计算任务去掉,虽然已经移除任务对象,但其他线程  
                //仍然能够获取到计算的结果,直到所有引用都失效,被垃圾回收掉  
                boolean success = cache.remove(cacheKey,f);  
                System.out.println(success);  
            }  
            return null;  
        }  
    }  
    package com.primer.demo.util;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.Callable;
    
    import net.sf.ehcache.Cache;
    
    public class CacheTest {
        private Cache cache;
    
        public CacheTest() {
            this.cache = EHCacheUtil.getCacheManager().getCache(EHCacheUtil.EHCACHE_ONEM_CACHE);
        }
    
        public List<Long> concurrentService(int para1, int param2) {
            long beginTime = System.nanoTime();
            final String cacheKey = "IamKey";
            List<Long> list = (List<Long>) EHCacheUtil.get(cacheKey);
            if (list == null) {
                Callable<Object> caller = new Callable<Object>() {
                    public Object call() throws InterruptedException {
                        System.out.println(" go to dao or rmi");
                        List<Long> list = new ArrayList<Long>();
                        list.add(1l);
                        list.add(2l);
                        // 将计算结果缓存
                        System.out.println("结果计算完毕,存入分布式缓存中");
                        EHCacheUtil.put(cacheKey, list);
                        Thread.sleep(500);
                        // 计算结果,通常是访问数据库或者远程服务
                        return list;
                    }
                };
                List<Long> result = (List<Long>) CacheTask.getInTask(cacheKey, caller);
                long end = System.nanoTime();
                // useTimes.add(end-beginTime);
                return result;
            } else {
                System.out.println("1.缓存命中,直接返回");
                long end = System.nanoTime();
                // useTimes.add(end-beginTime);
                return list;
            }
        }
    
        public static void main(String[] args) {
    
            new Thread(new Runnable() {
    
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    for (int i = 0; i < 50; i++) {
                        new CacheTest().concurrentService(1, 1);
                    }
                }
            }).start();
            new Thread(new Runnable() {
    
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    for (int i = 0; i < 50; i++) {
                        new CacheTest().concurrentService(1, 1);
                    }
                }
            }).start();
        }
    }
    

    完整代码请参考:https://github.com/465919283/demo

    展开全文
  • 在Web开发中,不可避免的是... 如果说当我们其中一个线程访问了成员变量Object后并且设置null,那么其他线程访问就会出现指针异常了。我接触线程安全问题的时候是在数据库的连接操作。刚刚学习Web开发,使用了JDB

       在Web开发中,不可避免的是需要遇到并发操作的,并发操作就有可能会引发我们的多线程安全问题。比如说,我们多线程下访问同一个变量并且有一个线程做出修改那么就会使得我们另外的线程在不知情的情况下被修改自己的数据。

       如果说当我们其中一个线程访问了成员变量Object后并且设置为null,那么其他线程访问就会出现空指针异常了。我接触线程安全问题的时候是在数据库的连接操作。刚刚学习Web开发,使用了JDBC操作,而且对于servlet还是很懵懂。在开发的时候我竟然将Connection设置为成员变量,然后有同学点醒了我,说这样会引发线程安全问题。所以,在学习Spring的时候,我也会问自己是否线程安全的问题。

    1. 多线程下单例有成员变量

    1.1. 测试

    首先这里有一个类User

    public class User {
    
        private int age;
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public long thisTime() {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().toString()+"age:"+age);
            age++;
            return System.currentTimeMillis();
        }
    }

    User中有一个方法thisTime 这个方法访问了age并作出了+1操作的修改。
    然后我们还有一个类MulThread是用于访问User的方法thisTime:

    public class MulThread extends Thread {
    
        @Resource(name = "user")
        private User user;
    
        @Override
        public void run() {
            for (int i = 0; i < 50; i++) {
                System.out.println(Thread.currentThread().toString()+user.thisTime());
            }
        }
    
        public User getUser() {
            return user;
        }
    
        public void setUser(User user) {
            this.user = user;
        }
    }

    现在我们可以开启线程用于测试模拟线程下,方法thisTime的访问情况,这里我们的具体实现如下:

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext.xml")
    public class TestMulThread {
    
        @Resource
        private MulThread th1;
        @Resource
        private MulThread th2;
    
        @Test
        public void test01() {
    
        System.out.println(""+th1+th1.getUser()+"\n"+th2+th2.getUser());
    
            th1.start();
            th2.start();
    
            //等待线程结束
            try {
                Thread.sleep(20000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    我们接下来在Spring配置Bean:

    <bean name="user" class="cn.gcc.domain.User" scope="singleton">
        <property name="age" value="18" />
    </bean>
    
    <bean class="cn.gcc.test.MulThread" scope="prototype"/>

    1.2. 结果

    Thread[Thread-2,5,main]cn.gcc.domain.User@cad498c
    Thread[Thread-3,5,main]cn.gcc.domain.User@cad498c
    Thread[Thread-2,5,main]age:18
    Thread[Thread-3,5,main]age:18
    Thread[Thread-2,5,main]1510027754876
    Thread[Thread-3,5,main]1510027754876
    Thread[Thread-3,5,main]age:20
    Thread[Thread-2,5,main]age:20
    .......

    看到上面的运行结果我们可以看到19直接被跳过了,因为age++被同时执行了两次。因此,结论就是如果是单例模式下如果方法访问了同一个成员变量那么就会引起线程不安全的问题。

    1.3. 修改

    我们现在如果想用单例但是又要线程安全的话该怎么办?修改如下:
    我们可以加锁synchronized我们只需要修改User的代码

    public synchronized long thisTime() {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().toString()+"age:"+age);
            age++;
            return System.currentTimeMillis();
        }
    Thread[Thread-2,5,main]cn.gcc.domain.User@cad498c
    Thread[Thread-3,5,main]cn.gcc.domain.User@cad498c
    Thread[Thread-3,5,main]age:18
    Thread[Thread-3,5,main]1510028011839
    Thread[Thread-2,5,main]age:19
    Thread[Thread-2,5,main]1510028011939
    Thread[Thread-3,5,main]age:20
    ....

    上面的运行结果就不会有执行两次age++的情况了,因为我们对User对象加锁操作,多个线程对同一个对象不能同时访问。

    当然除了上面的方法也可以修改我们的Spring配置信息,<bean name="user" class="cn.gcc.domain.User" scope="prototype">
    设置成多例就不会有这种问题了

    2. 多线程下SpringMVC的Controller

    我们在使用SpringMVC的时候默认Controller是单例的,因此,如果在设计Bean的时候是有成员变量的请参考上面所讲的 。

    还有一种就是我们方法中的参数引用的是Bean,那么这个引用是不是在多线程下是同一地址呢?
    现在我们写一个controller

    @Controller
    public class RegisterController {
    
        @RequestMapping(value = "/reg.action", method = RequestMethod.GET)
        public String reg(){
            return "reg.jsp";
        }
    
        @RequestMapping(value = "/reg.action", method = RequestMethod.POST)
        public String reg2(User user){
            System.out.println(user);
            return "reg.jsp";
        }
    }

    这个controller中在填入表单之后的提交过程中,form表单的各个元素SpringMVC都会帮我们封装,现在我们使用post请求去访问/reg.action然后看看结果是怎么样的
    这里写图片描述
    当我们使用了POST请求之后返回了reg.jsp的页面,控制台打印的信息是cn.gcc.domain.User@49cd75c3
    我们再次访问后的打印信息是cn.gcc.domain.User@555b1f19,因此,结论就是SpringMVC帮我们封装后的方法引用中是不会出现线程问题的

    这里SpringMVC的封装的机制我猜测是根据setter方法来封装的,而且每次封装都会通过反射机制重新新建一个对象并且注入表单的值

    下面是一些关于线程安全的链接

    http://blog.csdn.net/xiao__gui/article/details/8188833
    http://www.cnblogs.com/doit8791/p/4093808.html
    https://zhuanlan.zhihu.com/p/29587112
    http://blog.csdn.net/a236209186/article/details/61460211

    展开全文
  • 我们经常遇到一个情况,就是网络断开或程序Bug导致COMMIT/ROLLBACK语句没有传到数据库,也没有释放线程,但是线上事务锁定等待严重,连接数暴涨,尤其在测试库这种情况很,线上也偶有发生,于是想MySQL增加一个...
  • 我们经常遇到一个情况,就是网络断开或程序Bug导致COMMIT/ROLLBACK语句没有传到数据库,也没有释放线程,但是线上事务锁定等待严重,连接数暴涨,尤其在测试库这种情况很,线上也偶有发生,于是想MySQL增加一个...
  • 多线程断点下载原理

    2016-08-08 20:26:00
    服务器判断Id是否为空,不为空时表示是断点上传,从存储断点上传文件的数据库中根据文件Id查询文件保存的Path并将Path返回,根据Path从上次的历史文件中读取上传的断点位置(断点位置记录在临时文件的*.log文件里的...
  • 多线程-信号量Semaphore

    2017-06-12 14:16:00
    我们可以构造一个固定长度的资源池,当资源池为空的时候,请求资源将会阻塞,而不是失败。当资源池非空的时候解除阻塞。如果将Semaphore的计数值初始化为池的大小,在池中获取一个资源之前首先调用Semopore.acquire...
  • .net 多线程发送Email

    千次阅读 2008-07-23 12:06:00
    数据库表的设计:库名 x2mail1。手机类型适配表:表名 x2_mail_ada 因为发送给用户的xx信件的时候,需要知道该用户手机都支持什么样的铃声 和...不能为空) img_type_id int (该UA的图片适配号,从图铃列表中取) im
  • 数据库测试积累

    2009-01-21 16:38:50
    前段时间进行了关于数据库和后台线程的测试,让我学到了不少新知识(虽然代价也很大,漏报了一些BUG),因此将这些收集起来。...2、数据结构之间的差异,如某个字段在一边数据库中的值可以为空,而另一个库中的...
  • 数据库连接池啥要使用数据库连接池频繁的连接和断开数据库,消耗大,效率低DBUtils可以创建线程连接数据库,且一直保持连接,不会断开执行数据库操作时,由数据池分配线程,当数据池时,可选择等待或者抛错...
  • 当我们使用数据库连接池时,如果多线程连接数据库时,当超过5个线程连接数据库时,那么在第6个线程包括之后就连接不到数据库了,该怎么处理。 由于因为连接池的数量发生变化时,要去重新创建新的连接,所以这里使用...
  • 1、**修正网盘目录为空报错。 2、**为了皮肤调用方便写到模块里面了,请把SkinH.dll放到bin目录下面、 She:皮肤目录。 3、**服务端增加 置解密密码()、客户端增加 置加密密码()、尽量复杂些,这个即使是开发者...
  • 他在SQL Server专家联盟的董事会中服务了5年,SQL Server杂志供过稿,也在讨论SQL Server数据库编程的国际会议上发过言。  Louis Davidson,作为企业数据库开发人员和架构师,他拥有超过15年的工作经验。目前...
  • oracle数据库经典题目

    2011-02-17 15:05:20
    在Oracle数据库中,数据库的操作模式分为专用服务器(DELICATED SERVER)模式和多线程服务器(MULTITHREADED SERVER)模式两种。其中,在专用服务器模式中为每个用户进程创建一个服务器进程,用户进程与服务器进程之间...
  • //判断请求的client_id不能为空 if(StrUtil.isEmpty(clientId) || StrUtil.isEmpty(type)){ throw new ReqBodyValidationException(); } } catch (Exception e) { e....
  • 使用互斥锁,首先判断Redis中存放的数据是否为空,如果为空则使用setnx对他加一个互斥锁,加锁成功的时候,从数据库读取数据设置到Redis中,然后删除setnx设置的锁,如果加锁失败,则认为其他线程已经将数据库的值...
  • # 如果线程重新被请求,那么请求将从缓存中读取,如果缓存中是的或者是新的请求,那么这个线程将被重新创建,如果有很新的线程, # 增加这个值可以改善系统性能.通过比较Connections和Threads_created状态的变量,...
  • 想做一个在线人数统计的功能: 通过ServletContextListener 的 contextInitialized()方法参数 中ServletContextEvent 对象获取 application 级别的context 对象,获取Context_Param ...没有多线程,没有考虑连接池
  • 最开始时使用for循环遍历执行查询操作,但是node异步执行的特点总是在for循环开始运行的时候重新开启一个线程来执行下一步操作,导致接口返回的数据为空 解决办法就是使用async来同步进行数据库查询操作 npm i...
  • `GROUP BY`子句为空时使用 Stream Aggregation 算子,提升性能 支持使用索引计算 `Max/Min` 函数 优化关联子查询处理算法,支持将更类型的关联子查询解关联并转化成 `Left Outer Join` 扩大 `IndexLookupJoin` ...
  • 尝试了使用handler给全局变量赋值,但是getDateFromURL方法中的list都为空, 本人菜鸟,刚学一个月,很东西都是跌跌撞撞摸索的,求大神指点啊。这地方卡了好久了。目前能想到的思路就两个:1,通过全局变量过度;2...
  • Spring mvc 指针错误

    2015-08-18 03:54:07
    获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效 保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设true,那么在尝试 获取连接失败后该数据源将申明已...
  • 在给SSM项目添加一个定时器时,需要用到service对数据库进行操作 ...百度找了一圈,说是在多线程时使用@Autowired总是获取不到bean,原因是:new thread不在spring容器中,也就无法获得spring中的bean对=象
  • 但是我在获取到用户对应的scheme以后需要进行多线程下的其他操作,这个时候其他线程里面得ThreadLocal就为空了 ![图片说明](https://img-ask.csdn.net/upload/201710/20/1508479232_724404.png) 求大神指导一下我...
  • 由于创建连接比较消耗时间和系统资源,这个程序又不能用数据库连接池,我每个线程都只维护一个connection连接,当这个连接失效或为空的时候,再重新获取一个。 之前我一直怀疑是同一个连接如果不每次关闭,就无法...
  • Python使用MySQLdb数据库后,如使用多线程,每个线程创建一个db链接,然后再各自创建一个游标cursor,其中第一个线程读一个表中数据为空,第二个写入该表一条数据并提交,第一个线程再读该表数据将仍然无法读出。...
  • http://blog.csdn.net/dong976209075/article/details/8802778经验总结:Python使用MySQLdb数据库后,如使用多线程,每个线程创建一个db链接,然后再各自创建一个游标cursor,其中第一个线程读一个表中数据为空,第...
  • 如果数据库查询对象为空,则不放进缓存。在高并发下,当第一个线程将数据放入缓存中之前,会出现线程数据库中进行查询的现象,即缓存穿透 解决方法:双重判断加同步代码块 固定语法: if(第一次对值进...

空空如也

空空如也

1 2 3 4 5 ... 10
收藏数 183
精华内容 73
关键字:

多线程数据库为空